Skip to main content

treemaker_core/
lib.rs

1//! Headless TreeMaker 5.0.1 model engine.
2//!
3//! `treemaker-core` is a Rust port of the model-only TreeMaker engine: stream
4//! I/O, tree/path feasibility, ALM optimization, polygon construction, crease
5//! pattern construction, and CP diagnostics. It does not include GUI,
6//! wxWidgets, printing, menus, or proprietary optimizer backends.
7//!
8//! The primary entry point is [`Tree`].
9//!
10//! ```no_run
11//! use treemaker_core::Tree;
12//!
13//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
14//! let text = std::fs::read_to_string("model.tmd5")?;
15//! let mut tree = Tree::from_tmd_str(&text)?;
16//!
17//! let summary = tree.summary();
18//! println!("nodes={}, paths={}", summary.nodes, summary.paths);
19//!
20//! tree.optimize_scale()?;
21//! tree.build_polys_and_crease_pattern()?;
22//!
23//! std::fs::write("out.tmd5", tree.to_tmd5_string())?;
24//! # Ok(())
25//! # }
26//! ```
27//!
28//! Numeric optimization parity is tolerance-based. The behavioral baseline is
29//! TreeMaker 5.0.1 with the distributable ALM backend.
30
31use serde::{Deserialize, Serialize};
32use std::collections::BTreeMap;
33
34mod nlco;
35
36/// Floating-point type used by TreeMaker geometry and optimization.
37pub type TmFloat = f64;
38
39const DIST_TOL: TmFloat = 1.0e-4;
40const MIN_EDGE_LENGTH: TmFloat = 0.01;
41const DEPTH_NOT_SET: TmFloat = -999.0;
42const DEGREES: TmFloat = 0.017453292519943296;
43const PI: TmFloat = std::f64::consts::PI;
44const TWO_PI: TmFloat = 2.0 * std::f64::consts::PI;
45const CONVEXITY_TOL: TmFloat = 1.0e-4;
46const MOVE_TOL: TmFloat = 1.0e-6;
47const VERTEX_TOL: TmFloat = 0.003;
48const CREASE_AXIAL: i32 = 0;
49const CREASE_GUSSET: i32 = 1;
50const CREASE_RIDGE: i32 = 2;
51const CREASE_UNFOLDED_HINGE: i32 = 3;
52const CREASE_FOLDED_HINGE: i32 = 4;
53const CREASE_PSEUDOHINGE: i32 = 5;
54const FOLD_FLAT: i32 = 0;
55const FOLD_MOUNTAIN: i32 = 1;
56const FOLD_VALLEY: i32 = 2;
57const FOLD_BORDER: i32 = 3;
58const FACET_NOT_ORIENTED: i32 = 0;
59const FACET_WHITE_UP: i32 = 1;
60const FACET_COLOR_UP: i32 = 2;
61const ROOT_FLAG_INELIGIBLE: i32 = 0;
62const ROOT_FLAG_NOT_YET: i32 = 1;
63const ROOT_FLAG_ALREADY_ADDED: i32 = 2;
64
65/// Structured error returned by parsing, validation, optimization, and build operations.
66#[derive(Debug, thiserror::Error)]
67pub enum TreeError {
68    #[error("parse error at byte {offset}: {message}")]
69    Parse { offset: usize, message: String },
70    #[error("bad {kind} reference index {index}; valid range is 1..={max}")]
71    BadReference {
72        kind: &'static str,
73        index: usize,
74        max: usize,
75    },
76    #[error("unsupported TreeMaker document version {0}")]
77    UnsupportedVersion(String),
78    #[error("unsupported operation: {0}")]
79    UnsupportedOperation(&'static str),
80    #[error("optimizer failed to converge: {0}")]
81    OptimizerConvergence(String),
82    #[error("invalid operation: {0}")]
83    InvalidOperation(&'static str),
84}
85
86impl TreeError {
87    /// Stable machine-readable code for CLI, wasm, and API consumers.
88    pub fn code(&self) -> &'static str {
89        match self {
90            TreeError::Parse { .. } => "parse",
91            TreeError::BadReference { .. } => "bad_reference",
92            TreeError::UnsupportedVersion(_) => "unsupported_version",
93            TreeError::UnsupportedOperation(_) => "unsupported_operation",
94            TreeError::OptimizerConvergence(_) => "optimizer_convergence",
95            TreeError::InvalidOperation(_) => "invalid_operation",
96        }
97    }
98}
99
100/// Crate-local result type.
101pub type Result<T> = std::result::Result<T, TreeError>;
102
103/// Two-dimensional point in paper coordinates.
104#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
105pub struct Point {
106    pub x: TmFloat,
107    pub y: TmFloat,
108}
109
110impl Point {
111    pub fn distance(self, other: Self) -> TmFloat {
112        let dx = self.x - other.x;
113        let dy = self.y - other.y;
114        (dx * dx + dy * dy).sqrt()
115    }
116}
117
118/// Owner reference using TreeMaker's 1-based external indices.
119#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
120pub enum OwnerRef {
121    Tree,
122    Node(usize),
123    Path(usize),
124    Poly(usize),
125}
126
127/// TreeMaker node record.
128#[derive(Debug, Clone, Serialize, Deserialize)]
129pub struct Node {
130    pub index: usize,
131    pub label: String,
132    pub loc: Point,
133    pub depth: TmFloat,
134    pub elevation: TmFloat,
135    pub is_leaf: bool,
136    pub is_sub: bool,
137    pub is_border: bool,
138    pub is_pinned: bool,
139    pub is_polygon: bool,
140    pub is_junction: bool,
141    pub is_conditioned: bool,
142    pub owned_vertices: Vec<usize>,
143    pub edges: Vec<usize>,
144    pub leaf_paths: Vec<usize>,
145    pub owner: OwnerRef,
146}
147
148/// TreeMaker edge record.
149#[derive(Debug, Clone, Serialize, Deserialize)]
150pub struct Edge {
151    pub index: usize,
152    pub label: String,
153    pub length: TmFloat,
154    pub strain: TmFloat,
155    pub stiffness: TmFloat,
156    pub is_pinned: bool,
157    pub is_conditioned: bool,
158    pub nodes: Vec<usize>,
159}
160
161impl Edge {
162    /// Edge length after applying TreeMaker strain.
163    pub fn strained_length(&self) -> TmFloat {
164        self.length * (1.0 + self.strain)
165    }
166}
167
168/// TreeMaker path record between a pair of nodes.
169#[derive(Debug, Clone, Serialize, Deserialize)]
170pub struct Path {
171    pub index: usize,
172    pub min_tree_length: TmFloat,
173    pub min_paper_length: TmFloat,
174    pub act_tree_length: TmFloat,
175    pub act_paper_length: TmFloat,
176    pub is_leaf: bool,
177    pub is_sub: bool,
178    pub is_feasible: bool,
179    pub is_active: bool,
180    pub is_border: bool,
181    pub is_polygon: bool,
182    pub is_conditioned: bool,
183    pub fwd_poly: Option<usize>,
184    pub bkd_poly: Option<usize>,
185    pub nodes: Vec<usize>,
186    pub edges: Vec<usize>,
187    pub outset_path: Option<usize>,
188    pub front_reduction: TmFloat,
189    pub back_reduction: TmFloat,
190    pub min_depth: TmFloat,
191    pub min_depth_dist: TmFloat,
192    pub owned_vertices: Vec<usize>,
193    pub owned_creases: Vec<usize>,
194    pub owner: OwnerRef,
195}
196
197/// Polygon or subpolygon generated during crease-pattern construction.
198#[derive(Debug, Clone, Serialize, Deserialize)]
199pub struct Poly {
200    pub index: usize,
201    pub centroid: Point,
202    pub is_sub_poly: bool,
203    pub ring_nodes: Vec<usize>,
204    pub ring_paths: Vec<usize>,
205    pub cross_paths: Vec<usize>,
206    pub inset_nodes: Vec<usize>,
207    pub spoke_paths: Vec<usize>,
208    pub ridge_path: Option<usize>,
209    pub node_locs: Vec<Point>,
210    pub local_root_vertices: Vec<usize>,
211    pub local_root_creases: Vec<usize>,
212    pub owned_nodes: Vec<usize>,
213    pub owned_paths: Vec<usize>,
214    pub owned_polys: Vec<usize>,
215    pub owned_creases: Vec<usize>,
216    pub owned_facets: Vec<usize>,
217    pub owner: OwnerRef,
218}
219
220/// Crease-pattern vertex.
221#[derive(Debug, Clone, Serialize, Deserialize)]
222pub struct Vertex {
223    pub index: usize,
224    pub loc: Point,
225    pub elevation: TmFloat,
226    pub is_border: bool,
227    pub tree_node: Option<usize>,
228    pub left_pseudohinge_mate: Option<usize>,
229    pub right_pseudohinge_mate: Option<usize>,
230    pub creases: Vec<usize>,
231    pub depth: TmFloat,
232    pub discrete_depth: usize,
233    pub cc_flag: i32,
234    pub st_flag: i32,
235    pub owner: OwnerRef,
236}
237
238/// Crease-pattern crease.
239#[derive(Debug, Clone, Serialize, Deserialize)]
240pub struct Crease {
241    pub index: usize,
242    pub kind: i32,
243    pub vertices: Vec<usize>,
244    pub fwd_facet: Option<usize>,
245    pub bkd_facet: Option<usize>,
246    pub fold: i32,
247    pub cc_flag: i32,
248    pub st_flag: i32,
249    pub owner: OwnerRef,
250}
251
252/// Crease-pattern facet.
253#[derive(Debug, Clone, Serialize, Deserialize)]
254pub struct Facet {
255    pub index: usize,
256    pub centroid: Point,
257    pub is_well_formed: bool,
258    pub vertices: Vec<usize>,
259    pub creases: Vec<usize>,
260    pub corridor_edge: Option<usize>,
261    pub head_facets: Vec<usize>,
262    pub tail_facets: Vec<usize>,
263    pub order: usize,
264    pub color: i32,
265    pub owner: OwnerRef,
266}
267
268/// TreeMaker condition wrapper.
269#[derive(Debug, Clone, Serialize, Deserialize)]
270pub struct Condition {
271    pub index: usize,
272    pub is_feasible: bool,
273    pub kind: ConditionKind,
274}
275
276/// Supported TreeMaker condition variants.
277#[derive(Debug, Clone, Serialize, Deserialize)]
278#[serde(tag = "type", rename_all = "snake_case")]
279pub enum ConditionKind {
280    NodeCombo {
281        node: usize,
282        to_symmetry_line: bool,
283        to_paper_edge: bool,
284        to_paper_corner: bool,
285        x_fixed: bool,
286        x_fix_value: TmFloat,
287        y_fixed: bool,
288        y_fix_value: TmFloat,
289    },
290    NodeFixed {
291        node: usize,
292        x_fixed: bool,
293        y_fixed: bool,
294        x_fix_value: TmFloat,
295        y_fix_value: TmFloat,
296    },
297    NodeOnCorner {
298        node: usize,
299    },
300    NodeOnEdge {
301        node: usize,
302    },
303    NodeSymmetric {
304        node: usize,
305    },
306    NodesPaired {
307        node1: usize,
308        node2: usize,
309    },
310    NodesCollinear {
311        node1: usize,
312        node2: usize,
313        node3: usize,
314    },
315    EdgeLengthFixed {
316        edge: usize,
317    },
318    EdgesSameStrain {
319        edge1: usize,
320        edge2: usize,
321    },
322    PathCombo {
323        node1: usize,
324        node2: usize,
325        is_angle_fixed: bool,
326        angle: TmFloat,
327        is_angle_quant: bool,
328        quant: usize,
329        quant_offset: TmFloat,
330    },
331    PathActive {
332        node1: usize,
333        node2: usize,
334    },
335    PathAngleFixed {
336        node1: usize,
337        node2: usize,
338        angle: TmFloat,
339    },
340    PathAngleQuant {
341        node1: usize,
342        node2: usize,
343        quant: usize,
344        quant_offset: TmFloat,
345    },
346}
347
348/// Complete TreeMaker model state.
349#[derive(Debug, Clone, Serialize, Deserialize)]
350pub struct Tree {
351    pub source_version: String,
352    pub paper_width: TmFloat,
353    pub paper_height: TmFloat,
354    pub scale: TmFloat,
355    pub has_symmetry: bool,
356    pub sym_loc: Point,
357    pub sym_angle: TmFloat,
358    pub is_feasible: bool,
359    pub is_polygon_valid: bool,
360    pub is_polygon_filled: bool,
361    pub is_vertex_depth_valid: bool,
362    pub is_facet_data_valid: bool,
363    pub is_local_root_connectable: bool,
364    pub needs_cleanup: bool,
365    pub nodes: Vec<Node>,
366    pub edges: Vec<Edge>,
367    pub paths: Vec<Path>,
368    pub polys: Vec<Poly>,
369    pub vertices: Vec<Vertex>,
370    pub creases: Vec<Crease>,
371    pub facets: Vec<Facet>,
372    pub conditions: Vec<Condition>,
373    pub owned_nodes: Vec<usize>,
374    pub owned_edges: Vec<usize>,
375    pub owned_paths: Vec<usize>,
376    pub owned_polys: Vec<usize>,
377}
378
379/// High-level crease-pattern status, matching TreeMaker's `GetCPStatus()`.
380#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
381#[serde(rename_all = "snake_case")]
382pub enum CPStatus {
383    HasFullCp,
384    EdgesTooShort,
385    PolysNotValid,
386    PolysNotFilled,
387    PolysMultipleIbps,
388    VerticesLackDepth,
389    FacetsNotValid,
390    NotLocalRootConnectable,
391}
392
393/// Detailed crease-pattern status report with offending part IDs when available.
394#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
395pub struct CPStatusReport {
396    pub status: CPStatus,
397    pub bad_edges: Vec<usize>,
398    pub bad_polys: Vec<usize>,
399    pub bad_vertices: Vec<usize>,
400    pub bad_creases: Vec<usize>,
401    pub bad_facets: Vec<usize>,
402}
403
404/// Stable summary of a [`Tree`] suitable for CLI/wasm output and regression tests.
405#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
406pub struct TreeSummary {
407    pub source_version: String,
408    pub paper_width: TmFloat,
409    pub paper_height: TmFloat,
410    pub scale: TmFloat,
411    pub has_symmetry: bool,
412    pub is_feasible: bool,
413    pub cp_status: CPStatus,
414    pub nodes: usize,
415    pub edges: usize,
416    pub paths: usize,
417    pub polys: usize,
418    pub vertices: usize,
419    pub creases: usize,
420    pub facets: usize,
421    pub conditions: usize,
422    pub leaf_nodes: usize,
423    pub leaf_paths: usize,
424    pub feasible_paths: usize,
425    pub active_paths: usize,
426    pub border_nodes: usize,
427    pub border_paths: usize,
428    pub polygon_nodes: usize,
429    pub polygon_paths: usize,
430    pub pinned_nodes: usize,
431    pub pinned_edges: usize,
432    pub conditioned_nodes: usize,
433    pub conditioned_edges: usize,
434    pub conditioned_paths: usize,
435    pub conditions_by_tag: BTreeMap<String, usize>,
436}
437
438/// Report returned by optimization operations.
439#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
440pub struct OptimizationReport {
441    pub kind: OptimizationKind,
442    pub converged: bool,
443    pub old_scale: TmFloat,
444    pub new_scale: TmFloat,
445    pub is_feasible: bool,
446    pub message: String,
447}
448
449/// Optimizer kind used for an [`OptimizationReport`].
450#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
451#[serde(rename_all = "snake_case")]
452pub enum OptimizationKind {
453    Scale,
454    Edge,
455    Strain,
456}
457
458#[derive(Debug, Clone)]
459struct RootNetwork {
460    discrete_depth: usize,
461    is_connectable: bool,
462    cc_vertices: Vec<usize>,
463    cc_creases: Vec<usize>,
464    cc_polys: Vec<usize>,
465    st_vertices: Vec<usize>,
466    st_creases: Vec<usize>,
467    cc0: Vec<usize>,
468    cc1: Vec<usize>,
469    cc2_st1: Vec<usize>,
470    cc2_st2: Vec<usize>,
471}
472
473impl RootNetwork {
474    fn new(discrete_depth: usize) -> Self {
475        Self {
476            discrete_depth,
477            is_connectable: false,
478            cc_vertices: Vec::new(),
479            cc_creases: Vec::new(),
480            cc_polys: Vec::new(),
481            st_vertices: Vec::new(),
482            st_creases: Vec::new(),
483            cc0: Vec::new(),
484            cc1: Vec::new(),
485            cc2_st1: Vec::new(),
486            cc2_st2: Vec::new(),
487        }
488    }
489}
490
491impl CPStatusReport {
492    fn new(status: CPStatus) -> Self {
493        Self {
494            status,
495            bad_edges: Vec::new(),
496            bad_polys: Vec::new(),
497            bad_vertices: Vec::new(),
498            bad_creases: Vec::new(),
499            bad_facets: Vec::new(),
500        }
501    }
502
503    fn with_bad_edges(mut self, bad_edges: Vec<usize>) -> Self {
504        self.bad_edges = bad_edges;
505        self
506    }
507
508    fn with_bad_polys(mut self, bad_polys: Vec<usize>) -> Self {
509        self.bad_polys = bad_polys;
510        self
511    }
512
513    fn with_bad_vertices(mut self, bad_vertices: Vec<usize>) -> Self {
514        self.bad_vertices = bad_vertices;
515        self
516    }
517
518    fn with_bad_creases(mut self, bad_creases: Vec<usize>) -> Self {
519        self.bad_creases = bad_creases;
520        self
521    }
522
523    fn with_bad_facets(mut self, bad_facets: Vec<usize>) -> Self {
524        self.bad_facets = bad_facets;
525        self
526    }
527}
528
529impl Tree {
530    /// Parse a TreeMaker v3, v4, or v5 document from its ASCII stream format.
531    pub fn from_tmd_str(input: &str) -> Result<Self> {
532        let mut reader = Reader::new(input);
533        reader.expect_tag("tree")?;
534        let version = reader.read_token("version")?;
535        let tree = match version.as_str() {
536            "4.0" => {
537                let mut tree = Self::read_v4(&mut reader, version)?;
538                tree.validate()?;
539                tree.cleanup_after_edit();
540                tree
541            }
542            "5.0" => {
543                let tree = Self::read_v5(&mut reader, version)?;
544                tree.validate()?;
545                tree
546            }
547            "3.0" => {
548                let mut tree = Self::read_v3(&mut reader, version)?;
549                tree.validate()?;
550                tree.cleanup_after_edit();
551                tree
552            }
553            _ => return Err(TreeError::UnsupportedVersion(version)),
554        };
555        Ok(tree)
556    }
557
558    /// Serialize this tree to canonical TreeMaker v5 text.
559    pub fn to_tmd5_string(&self) -> String {
560        let mut out = Writer::new(10, "\n");
561        out.s("tree");
562        out.s("5.0");
563        out.f(self.paper_width);
564        out.f(self.paper_height);
565        out.f(self.scale);
566        out.b(self.has_symmetry);
567        out.point(self.sym_loc);
568        out.f(self.sym_angle);
569        out.b(self.is_feasible);
570        out.b(self.is_polygon_valid);
571        out.b(self.is_polygon_filled);
572        out.b(self.is_vertex_depth_valid);
573        out.b(self.is_facet_data_valid);
574        out.b(self.is_local_root_connectable);
575        out.b(self.needs_cleanup);
576        out.u(self.nodes.len());
577        out.u(self.edges.len());
578        out.u(self.paths.len());
579        out.u(self.polys.len());
580        out.u(self.vertices.len());
581        out.u(self.creases.len());
582        out.u(self.facets.len());
583        out.u(self.conditions.len());
584
585        for node in &self.nodes {
586            out.node_v5(node);
587        }
588        for edge in &self.edges {
589            out.edge(edge);
590        }
591        for path in &self.paths {
592            out.path_v5(path);
593        }
594        for poly in &self.polys {
595            out.poly_v5(poly);
596        }
597        for vertex in &self.vertices {
598            out.vertex_v5(vertex);
599        }
600        for crease in &self.creases {
601            out.crease_v5(crease);
602        }
603        for facet in &self.facets {
604            out.facet_v5(facet);
605        }
606        for condition in &self.conditions {
607            out.condition_v5(condition);
608        }
609        out.array(&self.owned_nodes);
610        out.array(&self.owned_edges);
611        out.array(&self.owned_paths);
612        out.array(&self.owned_polys);
613        out.finish()
614    }
615
616    /// Export this tree to TreeMaker v4 text for compatibility.
617    pub fn export_v4_string(&self) -> String {
618        let mut out = Writer::new(6, "\r");
619        out.s("tree");
620        out.s("4.0");
621        out.f(self.paper_width);
622        out.f(self.paper_height);
623        out.f(self.scale);
624        out.b(self.has_symmetry);
625        out.point(self.sym_loc);
626        out.f(self.sym_angle);
627        out.u(self.nodes.len());
628        out.u(self.edges.len());
629        out.u(self.paths.len());
630        out.u(0);
631        out.u(0);
632        out.u(0);
633        out.u(self.conditions.len());
634        for node in &self.nodes {
635            out.node_v4(node);
636        }
637        for edge in &self.edges {
638            out.edge(edge);
639        }
640        for path in &self.paths {
641            out.path_v4(path);
642        }
643        for condition in &self.conditions {
644            out.condition_v4(condition);
645        }
646        out.array(&self.owned_nodes);
647        out.array(&self.owned_edges);
648        out.array(&self.owned_paths);
649        out.u(0);
650        out.finish()
651    }
652
653    /// Return a stable structural and status summary.
654    pub fn summary(&self) -> TreeSummary {
655        let mut conditions_by_tag = BTreeMap::new();
656        for condition in &self.conditions {
657            *conditions_by_tag
658                .entry(condition.kind.tag().to_string())
659                .or_insert(0) += 1;
660        }
661        TreeSummary {
662            source_version: self.source_version.clone(),
663            paper_width: self.paper_width,
664            paper_height: self.paper_height,
665            scale: self.scale,
666            has_symmetry: self.has_symmetry,
667            is_feasible: self.is_feasible,
668            cp_status: self.cp_status(),
669            nodes: self.nodes.len(),
670            edges: self.edges.len(),
671            paths: self.paths.len(),
672            polys: self.polys.len(),
673            vertices: self.vertices.len(),
674            creases: self.creases.len(),
675            facets: self.facets.len(),
676            conditions: self.conditions.len(),
677            leaf_nodes: self.nodes.iter().filter(|n| n.is_leaf).count(),
678            leaf_paths: self.paths.iter().filter(|p| p.is_leaf).count(),
679            feasible_paths: self.paths.iter().filter(|p| p.is_feasible).count(),
680            active_paths: self.paths.iter().filter(|p| p.is_active).count(),
681            border_nodes: self.nodes.iter().filter(|n| n.is_border).count(),
682            border_paths: self.paths.iter().filter(|p| p.is_border).count(),
683            polygon_nodes: self.nodes.iter().filter(|n| n.is_polygon).count(),
684            polygon_paths: self.paths.iter().filter(|p| p.is_polygon).count(),
685            pinned_nodes: self.nodes.iter().filter(|n| n.is_pinned).count(),
686            pinned_edges: self.edges.iter().filter(|e| e.is_pinned).count(),
687            conditioned_nodes: self.nodes.iter().filter(|n| n.is_conditioned).count(),
688            conditioned_edges: self.edges.iter().filter(|e| e.is_conditioned).count(),
689            conditioned_paths: self.paths.iter().filter(|p| p.is_conditioned).count(),
690            conditions_by_tag,
691        }
692    }
693
694    /// Return the current feasibility flag.
695    pub fn is_feasible(&self) -> bool {
696        self.is_feasible
697    }
698
699    /// Return the current high-level crease-pattern status.
700    pub fn cp_status(&self) -> CPStatus {
701        if self
702            .edges
703            .iter()
704            .any(|edge| edge.strained_length() < MIN_EDGE_LENGTH)
705        {
706            return CPStatus::EdgesTooShort;
707        }
708        if !self.is_polygon_valid {
709            return CPStatus::PolysNotValid;
710        }
711        if !self.is_polygon_filled {
712            return CPStatus::PolysNotFilled;
713        }
714        if self.owned_polys.iter().copied().any(|poly_id| {
715            self.polys[poly_id - 1]
716                .ring_paths
717                .iter()
718                .filter(|path_id| !self.paths[**path_id - 1].is_active)
719                .count()
720                > 1
721        }) {
722            return CPStatus::PolysMultipleIbps;
723        }
724        if !self.is_vertex_depth_valid {
725            return CPStatus::VerticesLackDepth;
726        }
727        if !self.is_facet_data_valid {
728            return CPStatus::FacetsNotValid;
729        }
730        if !self.is_local_root_connectable {
731            return CPStatus::NotLocalRootConnectable;
732        }
733        CPStatus::HasFullCp
734    }
735
736    /// Return crease-pattern status plus bad part IDs where TreeMaker reports them.
737    pub fn cp_status_report(&self) -> CPStatusReport {
738        let bad_edges: Vec<_> = self
739            .edges
740            .iter()
741            .filter(|edge| edge.strained_length() < MIN_EDGE_LENGTH)
742            .map(|edge| edge.index)
743            .collect();
744        if !bad_edges.is_empty() {
745            return CPStatusReport::new(CPStatus::EdgesTooShort).with_bad_edges(bad_edges);
746        }
747
748        if !self.is_polygon_valid {
749            return CPStatusReport::new(CPStatus::PolysNotValid);
750        }
751
752        if !self.is_polygon_filled {
753            let bad_polys = self
754                .owned_polys
755                .iter()
756                .copied()
757                .filter(|poly_id| self.polys[*poly_id - 1].owned_nodes.is_empty())
758                .collect();
759            return CPStatusReport::new(CPStatus::PolysNotFilled).with_bad_polys(bad_polys);
760        }
761
762        let bad_polys: Vec<_> = self
763            .owned_polys
764            .iter()
765            .copied()
766            .filter(|poly_id| {
767                self.polys[*poly_id - 1]
768                    .ring_paths
769                    .iter()
770                    .filter(|path_id| !self.paths[**path_id - 1].is_active)
771                    .count()
772                    > 1
773            })
774            .collect();
775        if !bad_polys.is_empty() {
776            return CPStatusReport::new(CPStatus::PolysMultipleIbps).with_bad_polys(bad_polys);
777        }
778
779        if !self.is_vertex_depth_valid {
780            let bad_vertices = self
781                .vertices
782                .iter()
783                .filter(|vertex| vertex.depth == DEPTH_NOT_SET)
784                .map(|vertex| vertex.index)
785                .collect();
786            return CPStatusReport::new(CPStatus::VerticesLackDepth)
787                .with_bad_vertices(bad_vertices);
788        }
789
790        if !self.is_facet_data_valid {
791            let bad_vertices = self
792                .vertices
793                .iter()
794                .filter(|vertex| !vertex.is_border && vertex.creases.len() % 2 != 0)
795                .map(|vertex| vertex.index)
796                .collect();
797            let bad_facets = self
798                .facets
799                .iter()
800                .filter(|facet| !facet.is_well_formed)
801                .map(|facet| facet.index)
802                .collect();
803            return CPStatusReport::new(CPStatus::FacetsNotValid)
804                .with_bad_vertices(bad_vertices)
805                .with_bad_facets(bad_facets);
806        }
807
808        if !self.is_local_root_connectable {
809            let (bad_vertices, bad_creases) = self.why_not_local_root_connectable();
810            return CPStatusReport::new(CPStatus::NotLocalRootConnectable)
811                .with_bad_vertices(bad_vertices)
812                .with_bad_creases(bad_creases);
813        }
814
815        CPStatusReport::new(CPStatus::HasFullCp)
816    }
817
818    /// Run TreeMaker's ALM scale optimizer.
819    pub fn optimize_scale(&mut self) -> Result<OptimizationReport> {
820        let old_scale = self.scale;
821        let leaf_nodes = self.leaf_nodes_in_owned_order();
822        let num_vars = 1 + 2 * leaf_nodes.len();
823
824        let mut state = vec![0.0; num_vars];
825        state[0] = self.scale;
826        let mut node_offsets = vec![None; self.nodes.len() + 1];
827        for (i, node_id) in leaf_nodes.iter().copied().enumerate() {
828            let offset = 1 + 2 * i;
829            node_offsets[node_id] = Some(offset);
830            let loc = self.nodes[node_id - 1].loc;
831            state[offset] = loc.x;
832            state[offset + 1] = loc.y;
833        }
834
835        let mut optimizer = nlco::NlcoAlm::new(num_vars);
836        let mut lower_bounds = vec![0.0; num_vars];
837        let mut upper_bounds = vec![0.0; num_vars];
838        upper_bounds[0] = 2.0;
839        for i in 0..leaf_nodes.len() {
840            lower_bounds[1 + 2 * i] = 0.0;
841            lower_bounds[2 + 2 * i] = 0.0;
842            upper_bounds[1 + 2 * i] = self.paper_width;
843            upper_bounds[2 + 2 * i] = self.paper_height;
844        }
845        optimizer.set_bounds(lower_bounds, upper_bounds);
846        optimizer.set_objective(Box::new(ScaleObjective));
847        optimizer.add_linear_inequality(Box::new(nlco::OneVarFn::new(0, -1.0, 0.1 * self.scale)));
848
849        for path_id in &self.owned_paths {
850            let path = &self.paths[*path_id - 1];
851            if !path.is_leaf || self.has_path_active_base_condition(path) {
852                continue;
853            }
854            self.add_scale_path_constraint(
855                &mut optimizer,
856                &node_offsets,
857                path.nodes[0],
858                *path.nodes.last().unwrap(),
859                path.min_tree_length,
860                false,
861            );
862        }
863
864        for condition in &self.conditions {
865            self.add_scale_condition_constraints(&mut optimizer, &node_offsets, &condition.kind);
866        }
867
868        let inform = optimizer.minimize(&mut state);
869        if inform != 0 {
870            return Err(TreeError::OptimizerConvergence(format!(
871                "ALM returned result code {inform}"
872            )));
873        }
874
875        self.scale = state[0];
876        for (i, node_id) in leaf_nodes.into_iter().enumerate() {
877            let offset = 1 + 2 * i;
878            self.nodes[node_id - 1].loc = Point {
879                x: state[offset],
880                y: state[offset + 1],
881            };
882        }
883        self.cleanup_after_edit();
884
885        Ok(OptimizationReport {
886            kind: OptimizationKind::Scale,
887            converged: true,
888            old_scale,
889            new_scale: self.scale,
890            is_feasible: self.is_feasible,
891            message: "ALM scale optimization converged".to_string(),
892        })
893    }
894
895    /// Run TreeMaker's ALM edge-strain maximization optimizer.
896    pub fn optimize_edges(&mut self) -> Result<OptimizationReport> {
897        let old_scale = self.scale;
898        let moving_nodes = self.moving_nodes_for_edge_optimizer();
899        let stretchy_edges = self.stretchy_edges_for_edge_optimizer();
900        if moving_nodes.is_empty() {
901            return Err(TreeError::InvalidOperation(
902                "edge optimization has no moving leaf nodes",
903            ));
904        }
905        if stretchy_edges.is_empty() {
906            return Err(TreeError::InvalidOperation(
907                "edge optimization has no stretchy edges",
908            ));
909        }
910
911        let num_vars = 1 + 2 * moving_nodes.len();
912        let mut state = vec![0.0; num_vars];
913        let mut node_offsets = vec![None; self.nodes.len() + 1];
914        for (i, node_id) in moving_nodes.iter().copied().enumerate() {
915            let offset = 1 + 2 * i;
916            node_offsets[node_id] = Some(offset);
917            let loc = self.nodes[node_id - 1].loc;
918            state[offset] = loc.x;
919            state[offset + 1] = loc.y;
920        }
921        let stretchy_lookup = self.edge_lookup(&stretchy_edges);
922
923        let mut optimizer = nlco::NlcoAlm::new(num_vars);
924        let mut lower_bounds = vec![0.0; num_vars];
925        let mut upper_bounds = vec![0.0; num_vars];
926        lower_bounds[0] = -0.999;
927        upper_bounds[0] = 10.0;
928        for i in 0..moving_nodes.len() {
929            upper_bounds[1 + 2 * i] = self.paper_width;
930            upper_bounds[2 + 2 * i] = self.paper_height;
931        }
932        optimizer.set_bounds(lower_bounds, upper_bounds);
933        optimizer.set_objective(Box::new(ScaleObjective));
934
935        for path_id in &self.owned_paths {
936            let path = &self.paths[*path_id - 1];
937            if !path.is_leaf || self.has_path_active_base_condition(path) {
938                continue;
939            }
940            self.add_edge_path_constraint(
941                &mut optimizer,
942                &node_offsets,
943                &stretchy_lookup,
944                path,
945                false,
946            );
947        }
948        for condition in &self.conditions {
949            self.add_edge_condition_constraints(
950                &mut optimizer,
951                &node_offsets,
952                &stretchy_lookup,
953                &condition.kind,
954            );
955        }
956
957        let inform = optimizer.minimize(&mut state);
958        if inform != 0 {
959            return Err(TreeError::OptimizerConvergence(format!(
960                "ALM returned result code {inform}"
961            )));
962        }
963
964        for (i, node_id) in moving_nodes.into_iter().enumerate() {
965            let offset = 1 + 2 * i;
966            self.nodes[node_id - 1].loc = Point {
967                x: state[offset],
968                y: state[offset + 1],
969            };
970        }
971        for edge_id in stretchy_edges {
972            self.edges[edge_id - 1].strain = state[0];
973        }
974        self.cleanup_after_edit();
975
976        Ok(OptimizationReport {
977            kind: OptimizationKind::Edge,
978            converged: true,
979            old_scale,
980            new_scale: self.scale,
981            is_feasible: self.is_feasible,
982            message: "ALM edge strain optimization converged".to_string(),
983        })
984    }
985
986    /// Run TreeMaker's ALM strain minimization optimizer.
987    pub fn optimize_strain(&mut self) -> Result<OptimizationReport> {
988        let old_scale = self.scale;
989        let moving_nodes = self.moving_nodes_for_strain_optimizer();
990        let stretchy_edges = self.owned_edges.clone();
991        if moving_nodes.is_empty() && stretchy_edges.is_empty() {
992            return Err(TreeError::InvalidOperation(
993                "strain optimization has no moving nodes or edges",
994            ));
995        }
996
997        let edge_offset = 2 * moving_nodes.len();
998        let num_vars = edge_offset + stretchy_edges.len();
999        let mut state = vec![0.0; num_vars];
1000        let mut node_offsets = vec![None; self.nodes.len() + 1];
1001        let mut edge_offsets = vec![None; self.edges.len() + 1];
1002
1003        for (i, node_id) in moving_nodes.iter().copied().enumerate() {
1004            let offset = 2 * i;
1005            node_offsets[node_id] = Some(offset);
1006            let loc = self.nodes[node_id - 1].loc;
1007            state[offset] = loc.x;
1008            state[offset + 1] = loc.y;
1009        }
1010        let mut stiffness = Vec::with_capacity(stretchy_edges.len());
1011        for (i, edge_id) in stretchy_edges.iter().copied().enumerate() {
1012            let offset = edge_offset + i;
1013            edge_offsets[edge_id] = Some(offset);
1014            state[offset] = self.edges[edge_id - 1].strain;
1015            let edge_stiffness = self.edges[edge_id - 1].stiffness;
1016            stiffness.push(if edge_stiffness <= 0.0 {
1017                1.0
1018            } else {
1019                edge_stiffness
1020            });
1021        }
1022
1023        let mut optimizer = nlco::NlcoAlm::new(num_vars);
1024        let mut lower_bounds = vec![0.0; num_vars];
1025        let mut upper_bounds = vec![0.0; num_vars];
1026        for i in 0..moving_nodes.len() {
1027            upper_bounds[2 * i] = self.paper_width;
1028            upper_bounds[2 * i + 1] = self.paper_height;
1029        }
1030        for i in edge_offset..num_vars {
1031            lower_bounds[i] = -0.999;
1032            upper_bounds[i] = 2.0;
1033        }
1034        optimizer.set_bounds(lower_bounds, upper_bounds);
1035        optimizer.set_objective(Box::new(StrainObjective {
1036            edge_offset,
1037            stiffness,
1038        }));
1039
1040        for path_id in &self.owned_paths {
1041            let path = &self.paths[*path_id - 1];
1042            if !path.is_leaf || self.has_path_active_base_condition(path) {
1043                continue;
1044            }
1045            self.add_strain_path_constraint(
1046                &mut optimizer,
1047                &node_offsets,
1048                &edge_offsets,
1049                path,
1050                false,
1051            );
1052        }
1053        for condition in &self.conditions {
1054            self.add_strain_condition_constraints(
1055                &mut optimizer,
1056                &node_offsets,
1057                &edge_offsets,
1058                &condition.kind,
1059            );
1060        }
1061
1062        let inform = optimizer.minimize(&mut state);
1063        if inform != 0 {
1064            return Err(TreeError::OptimizerConvergence(format!(
1065                "ALM returned result code {inform}"
1066            )));
1067        }
1068
1069        for (i, node_id) in moving_nodes.into_iter().enumerate() {
1070            let offset = 2 * i;
1071            self.nodes[node_id - 1].loc = Point {
1072                x: state[offset],
1073                y: state[offset + 1],
1074            };
1075        }
1076        for (i, edge_id) in stretchy_edges.into_iter().enumerate() {
1077            self.edges[edge_id - 1].strain = state[edge_offset + i];
1078        }
1079        self.cleanup_after_edit();
1080
1081        Ok(OptimizationReport {
1082            kind: OptimizationKind::Strain,
1083            converged: true,
1084            old_scale,
1085            new_scale: self.scale,
1086            is_feasible: self.is_feasible,
1087            message: "ALM strain optimization converged".to_string(),
1088        })
1089    }
1090
1091    /// Build TreeMaker polygons without building full crease-pattern contents.
1092    pub fn build_tree_polys(&mut self) -> Result<()> {
1093        let leaf_paths = self.leaf_paths_in_owned_order();
1094        let border_nodes: Vec<usize> = self
1095            .owned_nodes
1096            .iter()
1097            .copied()
1098            .filter(|id| self.nodes[*id - 1].is_border)
1099            .collect();
1100        self.build_polys_from_paths(&leaf_paths, &border_nodes, OwnerRef::Tree)?;
1101
1102        let leaf_nodes = self.leaf_nodes_in_owned_order();
1103        let doomed: Vec<usize> = self
1104            .owned_polys
1105            .iter()
1106            .copied()
1107            .filter(|poly_id| {
1108                let Some(poly) = self.polys.get(poly_id.saturating_sub(1)) else {
1109                    return true;
1110                };
1111                !self.poly_is_convex(poly) || self.poly_encloses_leaf_node(poly, &leaf_nodes)
1112            })
1113            .collect();
1114        self.delete_polys(&doomed);
1115        self.cleanup_after_edit();
1116        Ok(())
1117    }
1118
1119    /// Build polygons, vertices, creases, facets, facet order, color, and fold data.
1120    pub fn build_polys_and_crease_pattern(&mut self) -> Result<()> {
1121        self.build_tree_polys()?;
1122        if self
1123            .edges
1124            .iter()
1125            .any(|edge| edge.strained_length() < MIN_EDGE_LENGTH)
1126        {
1127            return Ok(());
1128        }
1129
1130        let owned_polys = self.owned_polys.clone();
1131        for poly_id in owned_polys {
1132            self.build_poly_contents_geometry(poly_id)?;
1133        }
1134        self.cleanup_after_edit();
1135        Ok(())
1136    }
1137
1138    #[doc(hidden)]
1139    #[doc(hidden)]
1140    pub fn build_polygon_contents_for_oracle_tests(&mut self) -> Result<()> {
1141        self.build_tree_polys()?;
1142        if self
1143            .edges
1144            .iter()
1145            .any(|edge| edge.strained_length() < MIN_EDGE_LENGTH)
1146        {
1147            return Ok(());
1148        }
1149
1150        let owned_polys = self.owned_polys.clone();
1151        for poly_id in owned_polys {
1152            self.build_poly_contents_geometry(poly_id)?;
1153        }
1154        self.cleanup_after_edit();
1155        Ok(())
1156    }
1157
1158    fn read_v3(reader: &mut Reader<'_>, source_version: String) -> Result<Self> {
1159        let paper_width = reader.read_f64("paper width")?;
1160        let paper_height = reader.read_f64("paper height")?;
1161        let scale = reader.read_f64("scale")?;
1162        let has_symmetry = reader.read_bool("has symmetry")?;
1163        let sym_loc = reader.read_point("symmetry location")?;
1164        let sym_angle = reader.read_f64("symmetry angle")?;
1165        let num_nodes = reader.read_usize("node count")?;
1166        let num_edges = reader.read_usize("edge count")?;
1167        let num_paths = reader.read_usize("path count")?;
1168        let _num_polys = reader.read_usize("poly count")?;
1169
1170        let mut nodes = Vec::with_capacity(num_nodes);
1171        let mut edges = Vec::with_capacity(num_edges);
1172        let mut paths = Vec::with_capacity(num_paths);
1173        let mut conditions = Vec::new();
1174
1175        for _ in 0..num_nodes {
1176            nodes.push(reader.read_node_v3(&mut conditions)?);
1177        }
1178        for _ in 0..num_edges {
1179            edges.push(reader.read_edge_v3()?);
1180        }
1181        for _ in 0..num_paths {
1182            paths.push(reader.read_path_v3(&mut conditions)?);
1183        }
1184
1185        Ok(Self {
1186            source_version,
1187            paper_width,
1188            paper_height,
1189            scale,
1190            has_symmetry,
1191            sym_loc,
1192            sym_angle,
1193            is_feasible: false,
1194            is_polygon_valid: false,
1195            is_polygon_filled: false,
1196            is_vertex_depth_valid: false,
1197            is_facet_data_valid: false,
1198            is_local_root_connectable: false,
1199            needs_cleanup: false,
1200            nodes,
1201            edges,
1202            paths,
1203            polys: Vec::new(),
1204            vertices: Vec::new(),
1205            creases: Vec::new(),
1206            facets: Vec::new(),
1207            conditions,
1208            owned_nodes: (1..=num_nodes).collect(),
1209            owned_edges: (1..=num_edges).collect(),
1210            owned_paths: (1..=num_paths).collect(),
1211            owned_polys: Vec::new(),
1212        })
1213    }
1214
1215    fn read_v4(reader: &mut Reader<'_>, source_version: String) -> Result<Self> {
1216        let paper_width = reader.read_f64("paper width")?;
1217        let paper_height = reader.read_f64("paper height")?;
1218        let scale = reader.read_f64("scale")?;
1219        let has_symmetry = reader.read_bool("has symmetry")?;
1220        let sym_loc = reader.read_point("symmetry location")?;
1221        let sym_angle = reader.read_f64("symmetry angle")?;
1222        let num_nodes = reader.read_usize("node count")?;
1223        let num_edges = reader.read_usize("edge count")?;
1224        let num_paths = reader.read_usize("path count")?;
1225        let num_polys = reader.read_usize("poly count")?;
1226        let num_vertices = reader.read_usize("vertex count")?;
1227        let num_creases = reader.read_usize("crease count")?;
1228        let num_conditions = reader.read_usize("condition count")?;
1229
1230        let mut nodes = Vec::with_capacity(num_nodes);
1231        let mut edges = Vec::with_capacity(num_edges);
1232        let mut paths = Vec::with_capacity(num_paths);
1233        let mut polys = Vec::with_capacity(num_polys);
1234        let mut vertices = Vec::with_capacity(num_vertices);
1235        let mut creases = Vec::with_capacity(num_creases);
1236
1237        for _ in 0..num_nodes {
1238            nodes.push(reader.read_node_v4()?);
1239        }
1240        for _ in 0..num_edges {
1241            edges.push(reader.read_edge(true)?);
1242        }
1243        for _ in 0..num_paths {
1244            paths.push(reader.read_path_v4(num_polys)?);
1245        }
1246        for _ in 0..num_polys {
1247            polys.push(reader.read_poly_v4(num_paths)?);
1248        }
1249        for index in 1..=num_vertices {
1250            vertices.push(reader.read_vertex_v4(index)?);
1251        }
1252        for index in 1..=num_creases {
1253            creases.push(reader.read_crease_v4(index)?);
1254        }
1255
1256        let mut conditions = Vec::with_capacity(num_conditions);
1257        for i in 0..num_conditions {
1258            conditions.push(reader.read_condition_v4(i + 1)?);
1259        }
1260
1261        let owned_nodes = reader.read_index_array("owned nodes")?;
1262        let owned_edges = reader.read_index_array("owned edges")?;
1263        let owned_paths = reader.read_index_array("owned paths")?;
1264        let _owned_polys = reader.read_index_array("owned polys")?;
1265
1266        kill_v4_crease_pattern_refs(&mut nodes, &mut paths);
1267
1268        Ok(Self {
1269            source_version,
1270            paper_width,
1271            paper_height,
1272            scale,
1273            has_symmetry,
1274            sym_loc,
1275            sym_angle,
1276            is_feasible: false,
1277            is_polygon_valid: false,
1278            is_polygon_filled: false,
1279            is_vertex_depth_valid: false,
1280            is_facet_data_valid: false,
1281            is_local_root_connectable: false,
1282            needs_cleanup: false,
1283            nodes,
1284            edges,
1285            paths,
1286            polys: Vec::new(),
1287            vertices: Vec::new(),
1288            creases: Vec::new(),
1289            facets: Vec::new(),
1290            conditions,
1291            owned_nodes,
1292            owned_edges,
1293            owned_paths,
1294            owned_polys: Vec::new(),
1295        })
1296    }
1297
1298    fn read_v5(reader: &mut Reader<'_>, source_version: String) -> Result<Self> {
1299        let paper_width = reader.read_f64("paper width")?;
1300        let paper_height = reader.read_f64("paper height")?;
1301        let scale = reader.read_f64("scale")?;
1302        let has_symmetry = reader.read_bool("has symmetry")?;
1303        let sym_loc = reader.read_point("symmetry location")?;
1304        let sym_angle = reader.read_f64("symmetry angle")?;
1305        let is_feasible = reader.read_bool("feasible flag")?;
1306        let is_polygon_valid = reader.read_bool("polygon valid flag")?;
1307        let is_polygon_filled = reader.read_bool("polygon filled flag")?;
1308        let is_vertex_depth_valid = reader.read_bool("vertex depth valid flag")?;
1309        let is_facet_data_valid = reader.read_bool("facet data valid flag")?;
1310        let is_local_root_connectable = reader.read_bool("local root connectable flag")?;
1311        let needs_cleanup = reader.read_bool("needs cleanup flag")?;
1312        let num_nodes = reader.read_usize("node count")?;
1313        let num_edges = reader.read_usize("edge count")?;
1314        let num_paths = reader.read_usize("path count")?;
1315        let num_polys = reader.read_usize("poly count")?;
1316        let num_vertices = reader.read_usize("vertex count")?;
1317        let num_creases = reader.read_usize("crease count")?;
1318        let num_facets = reader.read_usize("facet count")?;
1319        let num_conditions = reader.read_usize("condition count")?;
1320
1321        let mut nodes = Vec::with_capacity(num_nodes);
1322        let mut edges = Vec::with_capacity(num_edges);
1323        let mut paths = Vec::with_capacity(num_paths);
1324        let mut polys = Vec::with_capacity(num_polys);
1325        let mut vertices = Vec::with_capacity(num_vertices);
1326        let mut creases = Vec::with_capacity(num_creases);
1327        let mut facets = Vec::with_capacity(num_facets);
1328
1329        for _ in 0..num_nodes {
1330            nodes.push(reader.read_node_v5()?);
1331        }
1332        for _ in 0..num_edges {
1333            edges.push(reader.read_edge(false)?);
1334        }
1335        for _ in 0..num_paths {
1336            paths.push(reader.read_path_v5(num_polys, num_paths)?);
1337        }
1338        for _ in 0..num_polys {
1339            polys.push(reader.read_poly_v5(num_paths)?);
1340        }
1341        for _ in 0..num_vertices {
1342            vertices.push(reader.read_vertex_v5(num_nodes, num_vertices)?);
1343        }
1344        for _ in 0..num_creases {
1345            creases.push(reader.read_crease_v5(num_facets)?);
1346        }
1347        for _ in 0..num_facets {
1348            facets.push(reader.read_facet_v5(num_edges)?);
1349        }
1350
1351        let mut conditions = Vec::with_capacity(num_conditions);
1352        for _ in 0..num_conditions {
1353            conditions.push(reader.read_condition_v5()?);
1354        }
1355
1356        let owned_nodes = reader.read_index_array("owned nodes")?;
1357        let owned_edges = reader.read_index_array("owned edges")?;
1358        let owned_paths = reader.read_index_array("owned paths")?;
1359        let owned_polys = reader.read_index_array("owned polys")?;
1360
1361        Ok(Self {
1362            source_version,
1363            paper_width,
1364            paper_height,
1365            scale,
1366            has_symmetry,
1367            sym_loc,
1368            sym_angle,
1369            is_feasible,
1370            is_polygon_valid,
1371            is_polygon_filled,
1372            is_vertex_depth_valid,
1373            is_facet_data_valid,
1374            is_local_root_connectable,
1375            needs_cleanup,
1376            nodes,
1377            edges,
1378            paths,
1379            polys,
1380            vertices,
1381            creases,
1382            facets,
1383            conditions,
1384            owned_nodes,
1385            owned_edges,
1386            owned_paths,
1387            owned_polys,
1388        })
1389    }
1390
1391    fn validate(&self) -> Result<()> {
1392        for id in &self.owned_nodes {
1393            self.check_ref("node", *id, self.nodes.len())?;
1394        }
1395        for id in &self.owned_edges {
1396            self.check_ref("edge", *id, self.edges.len())?;
1397        }
1398        for id in &self.owned_paths {
1399            self.check_ref("path", *id, self.paths.len())?;
1400        }
1401        for id in &self.owned_polys {
1402            self.check_ref("poly", *id, self.polys.len())?;
1403        }
1404        for node in &self.nodes {
1405            for id in &node.edges {
1406                self.check_ref("edge", *id, self.edges.len())?;
1407            }
1408            for id in &node.leaf_paths {
1409                self.check_ref("path", *id, self.paths.len())?;
1410            }
1411            for id in &node.owned_vertices {
1412                self.check_ref("vertex", *id, self.vertices.len())?;
1413            }
1414            self.check_owner(&node.owner)?;
1415        }
1416        for edge in &self.edges {
1417            for id in &edge.nodes {
1418                self.check_ref("node", *id, self.nodes.len())?;
1419            }
1420        }
1421        for path in &self.paths {
1422            for id in &path.nodes {
1423                self.check_ref("node", *id, self.nodes.len())?;
1424            }
1425            for id in &path.edges {
1426                self.check_ref("edge", *id, self.edges.len())?;
1427            }
1428            if let Some(id) = path.fwd_poly {
1429                self.check_ref("poly", id, self.polys.len())?;
1430            }
1431            if let Some(id) = path.bkd_poly {
1432                self.check_ref("poly", id, self.polys.len())?;
1433            }
1434            if let Some(id) = path.outset_path {
1435                self.check_ref("path", id, self.paths.len())?;
1436            }
1437            for id in &path.owned_vertices {
1438                self.check_ref("vertex", *id, self.vertices.len())?;
1439            }
1440            for id in &path.owned_creases {
1441                self.check_ref("crease", *id, self.creases.len())?;
1442            }
1443            self.check_owner(&path.owner)?;
1444        }
1445        for poly in &self.polys {
1446            for id in &poly.ring_nodes {
1447                self.check_ref("node", *id, self.nodes.len())?;
1448            }
1449            for id in &poly.inset_nodes {
1450                self.check_ref("node", *id, self.nodes.len())?;
1451            }
1452            for id in &poly.owned_nodes {
1453                self.check_ref("node", *id, self.nodes.len())?;
1454            }
1455            for id in poly
1456                .ring_paths
1457                .iter()
1458                .chain(&poly.cross_paths)
1459                .chain(&poly.spoke_paths)
1460                .chain(&poly.owned_paths)
1461            {
1462                self.check_ref("path", *id, self.paths.len())?;
1463            }
1464            if let Some(id) = poly.ridge_path {
1465                self.check_ref("path", id, self.paths.len())?;
1466            }
1467            for id in &poly.local_root_vertices {
1468                self.check_ref("vertex", *id, self.vertices.len())?;
1469            }
1470            for id in poly.local_root_creases.iter().chain(&poly.owned_creases) {
1471                self.check_ref("crease", *id, self.creases.len())?;
1472            }
1473            for id in &poly.owned_polys {
1474                self.check_ref("poly", *id, self.polys.len())?;
1475            }
1476            for id in &poly.owned_facets {
1477                self.check_ref("facet", *id, self.facets.len())?;
1478            }
1479            self.check_owner(&poly.owner)?;
1480        }
1481        for vertex in &self.vertices {
1482            if let Some(id) = vertex.tree_node {
1483                self.check_ref("node", id, self.nodes.len())?;
1484            }
1485            if let Some(id) = vertex.left_pseudohinge_mate {
1486                self.check_ref("vertex", id, self.vertices.len())?;
1487            }
1488            if let Some(id) = vertex.right_pseudohinge_mate {
1489                self.check_ref("vertex", id, self.vertices.len())?;
1490            }
1491            for id in &vertex.creases {
1492                self.check_ref("crease", *id, self.creases.len())?;
1493            }
1494            self.check_owner(&vertex.owner)?;
1495        }
1496        for crease in &self.creases {
1497            for id in &crease.vertices {
1498                self.check_ref("vertex", *id, self.vertices.len())?;
1499            }
1500            if let Some(id) = crease.fwd_facet {
1501                self.check_ref("facet", id, self.facets.len())?;
1502            }
1503            if let Some(id) = crease.bkd_facet {
1504                self.check_ref("facet", id, self.facets.len())?;
1505            }
1506            self.check_owner(&crease.owner)?;
1507        }
1508        for facet in &self.facets {
1509            for id in &facet.vertices {
1510                self.check_ref("vertex", *id, self.vertices.len())?;
1511            }
1512            for id in &facet.creases {
1513                self.check_ref("crease", *id, self.creases.len())?;
1514            }
1515            if let Some(id) = facet.corridor_edge {
1516                self.check_ref("edge", id, self.edges.len())?;
1517            }
1518            for id in facet.head_facets.iter().chain(&facet.tail_facets) {
1519                self.check_ref("facet", *id, self.facets.len())?;
1520            }
1521            self.check_owner(&facet.owner)?;
1522        }
1523        for condition in &self.conditions {
1524            condition.kind.validate_refs(self)?;
1525        }
1526        Ok(())
1527    }
1528
1529    fn check_ref(&self, kind: &'static str, index: usize, max: usize) -> Result<()> {
1530        if index == 0 || index > max {
1531            return Err(TreeError::BadReference { kind, index, max });
1532        }
1533        Ok(())
1534    }
1535
1536    fn check_owner(&self, owner: &OwnerRef) -> Result<()> {
1537        match *owner {
1538            OwnerRef::Tree => Ok(()),
1539            OwnerRef::Node(id) => self.check_ref("node", id, self.nodes.len()),
1540            OwnerRef::Path(id) => self.check_ref("path", id, self.paths.len()),
1541            OwnerRef::Poly(id) => self.check_ref("poly", id, self.polys.len()),
1542        }
1543    }
1544
1545    fn find_leaf_path_between(&self, node1: usize, node2: usize) -> Option<&Path> {
1546        self.paths.iter().find(|path| {
1547            path.is_leaf
1548                && matches!(
1549                    path.nodes.first().copied().zip(path.nodes.last().copied()),
1550                    Some((a, b)) if (a == node1 && b == node2) || (a == node2 && b == node1)
1551                )
1552        })
1553    }
1554
1555    fn leaf_nodes_in_owned_order(&self) -> Vec<usize> {
1556        self.owned_nodes
1557            .iter()
1558            .copied()
1559            .filter(|id| self.nodes[id - 1].is_leaf)
1560            .collect()
1561    }
1562
1563    fn has_path_active_base_condition(&self, path: &Path) -> bool {
1564        let Some((node1, node2)) = path.nodes.first().copied().zip(path.nodes.last().copied())
1565        else {
1566            return false;
1567        };
1568        self.conditions
1569            .iter()
1570            .any(|condition| match condition.kind {
1571                ConditionKind::PathActive { node1: a, node2: b }
1572                | ConditionKind::PathAngleFixed {
1573                    node1: a, node2: b, ..
1574                }
1575                | ConditionKind::PathAngleQuant {
1576                    node1: a, node2: b, ..
1577                } => (a == node1 && b == node2) || (a == node2 && b == node1),
1578                _ => false,
1579            })
1580    }
1581
1582    fn scale_offset(node_offsets: &[Option<usize>], node: usize) -> Option<usize> {
1583        node_offsets.get(node).copied().flatten()
1584    }
1585
1586    fn add_scale_path_constraint(
1587        &self,
1588        optimizer: &mut nlco::NlcoAlm,
1589        node_offsets: &[Option<usize>],
1590        node1: usize,
1591        node2: usize,
1592        min_tree_length: TmFloat,
1593        equality: bool,
1594    ) {
1595        let (Some(ix), Some(jx)) = (
1596            Self::scale_offset(node_offsets, node1),
1597            Self::scale_offset(node_offsets, node2),
1598        ) else {
1599            return;
1600        };
1601        let constraint = Box::new(nlco::PathFn1::new(ix, ix + 1, jx, jx + 1, min_tree_length));
1602        if equality {
1603            optimizer.add_nonlinear_equality(constraint);
1604        } else {
1605            optimizer.add_nonlinear_inequality(constraint);
1606        }
1607    }
1608
1609    fn add_scale_path_active_constraint(
1610        &self,
1611        optimizer: &mut nlco::NlcoAlm,
1612        node_offsets: &[Option<usize>],
1613        node1: usize,
1614        node2: usize,
1615    ) {
1616        if let Some(path) = self.find_leaf_path_between(node1, node2) {
1617            self.add_scale_path_constraint(
1618                optimizer,
1619                node_offsets,
1620                node1,
1621                node2,
1622                path.min_tree_length,
1623                true,
1624            );
1625        }
1626    }
1627
1628    fn add_scale_condition_constraints(
1629        &self,
1630        optimizer: &mut nlco::NlcoAlm,
1631        node_offsets: &[Option<usize>],
1632        kind: &ConditionKind,
1633    ) {
1634        match *kind {
1635            ConditionKind::NodeCombo {
1636                node,
1637                to_symmetry_line,
1638                to_paper_edge,
1639                to_paper_corner,
1640                x_fixed,
1641                x_fix_value,
1642                y_fixed,
1643                y_fix_value,
1644            } => {
1645                let Some(ix) = Self::scale_offset(node_offsets, node) else {
1646                    return;
1647                };
1648                let iy = ix + 1;
1649                if self.has_symmetry && to_symmetry_line {
1650                    optimizer.add_linear_equality(Box::new(nlco::StickToLineFn::new(
1651                        ix,
1652                        iy,
1653                        self.sym_loc.x,
1654                        self.sym_loc.y,
1655                        self.sym_angle,
1656                    )));
1657                }
1658                if to_paper_edge {
1659                    optimizer.add_nonlinear_equality(Box::new(nlco::StickToEdgeFn::new(
1660                        ix,
1661                        iy,
1662                        self.paper_width,
1663                        self.paper_height,
1664                    )));
1665                }
1666                if to_paper_corner {
1667                    optimizer.add_nonlinear_equality(Box::new(nlco::CornerFn::new(
1668                        ix,
1669                        self.paper_width,
1670                    )));
1671                    optimizer.add_nonlinear_equality(Box::new(nlco::CornerFn::new(
1672                        iy,
1673                        self.paper_height,
1674                    )));
1675                }
1676                if x_fixed {
1677                    optimizer.add_linear_equality(Box::new(nlco::OneVarFn::new(
1678                        ix,
1679                        -1.0,
1680                        x_fix_value,
1681                    )));
1682                }
1683                if y_fixed {
1684                    optimizer.add_linear_equality(Box::new(nlco::OneVarFn::new(
1685                        iy,
1686                        -1.0,
1687                        y_fix_value,
1688                    )));
1689                }
1690            }
1691            ConditionKind::NodeFixed {
1692                node,
1693                x_fixed,
1694                y_fixed,
1695                x_fix_value,
1696                y_fix_value,
1697            } => {
1698                let Some(ix) = Self::scale_offset(node_offsets, node) else {
1699                    return;
1700                };
1701                let iy = ix + 1;
1702                if x_fixed {
1703                    optimizer.add_linear_equality(Box::new(nlco::OneVarFn::new(
1704                        ix,
1705                        -1.0,
1706                        x_fix_value,
1707                    )));
1708                }
1709                if y_fixed {
1710                    optimizer.add_linear_equality(Box::new(nlco::OneVarFn::new(
1711                        iy,
1712                        -1.0,
1713                        y_fix_value,
1714                    )));
1715                }
1716            }
1717            ConditionKind::NodeOnCorner { node } => {
1718                if let Some(ix) = Self::scale_offset(node_offsets, node) {
1719                    optimizer.add_nonlinear_equality(Box::new(nlco::CornerFn::new(
1720                        ix,
1721                        self.paper_width,
1722                    )));
1723                    optimizer.add_nonlinear_equality(Box::new(nlco::CornerFn::new(
1724                        ix + 1,
1725                        self.paper_height,
1726                    )));
1727                }
1728            }
1729            ConditionKind::NodeOnEdge { node } => {
1730                if let Some(ix) = Self::scale_offset(node_offsets, node) {
1731                    optimizer.add_nonlinear_equality(Box::new(nlco::StickToEdgeFn::new(
1732                        ix,
1733                        ix + 1,
1734                        self.paper_width,
1735                        self.paper_height,
1736                    )));
1737                }
1738            }
1739            ConditionKind::NodeSymmetric { node } => {
1740                if !self.has_symmetry {
1741                    return;
1742                }
1743                if let Some(ix) = Self::scale_offset(node_offsets, node) {
1744                    optimizer.add_linear_equality(Box::new(nlco::StickToLineFn::new(
1745                        ix,
1746                        ix + 1,
1747                        self.sym_loc.x,
1748                        self.sym_loc.y,
1749                        self.sym_angle,
1750                    )));
1751                }
1752            }
1753            ConditionKind::NodesPaired { node1, node2 } => {
1754                if !self.has_symmetry {
1755                    return;
1756                }
1757                let (Some(ix), Some(jx)) = (
1758                    Self::scale_offset(node_offsets, node1),
1759                    Self::scale_offset(node_offsets, node2),
1760                ) else {
1761                    return;
1762                };
1763                optimizer.add_linear_equality(Box::new(nlco::PairFn1A::new(
1764                    ix,
1765                    ix + 1,
1766                    jx,
1767                    jx + 1,
1768                    self.sym_angle,
1769                )));
1770                optimizer.add_linear_equality(Box::new(nlco::PairFn1B::new(
1771                    ix,
1772                    ix + 1,
1773                    jx,
1774                    jx + 1,
1775                    self.sym_loc.x,
1776                    self.sym_loc.y,
1777                    self.sym_angle,
1778                )));
1779            }
1780            ConditionKind::NodesCollinear {
1781                node1,
1782                node2,
1783                node3,
1784            } => {
1785                let (Some(ix), Some(jx), Some(kx)) = (
1786                    Self::scale_offset(node_offsets, node1),
1787                    Self::scale_offset(node_offsets, node2),
1788                    Self::scale_offset(node_offsets, node3),
1789                ) else {
1790                    return;
1791                };
1792                optimizer.add_nonlinear_equality(Box::new(nlco::CollinearFn1::new(
1793                    ix,
1794                    ix + 1,
1795                    jx,
1796                    jx + 1,
1797                    kx,
1798                    kx + 1,
1799                )));
1800            }
1801            ConditionKind::EdgeLengthFixed { .. } | ConditionKind::EdgesSameStrain { .. } => {}
1802            ConditionKind::PathCombo {
1803                node1,
1804                node2,
1805                is_angle_fixed,
1806                angle,
1807                is_angle_quant,
1808                quant,
1809                quant_offset,
1810            } => {
1811                let (Some(ix), Some(jx)) = (
1812                    Self::scale_offset(node_offsets, node1),
1813                    Self::scale_offset(node_offsets, node2),
1814                ) else {
1815                    return;
1816                };
1817                self.add_scale_path_active_constraint(optimizer, node_offsets, node1, node2);
1818                if is_angle_fixed {
1819                    optimizer.add_linear_equality(Box::new(nlco::PathAngleFn1::new(
1820                        ix,
1821                        ix + 1,
1822                        jx,
1823                        jx + 1,
1824                        angle,
1825                    )));
1826                }
1827                if is_angle_quant {
1828                    optimizer.add_nonlinear_equality(Box::new(nlco::QuantizeAngleFn1::new(
1829                        ix,
1830                        ix + 1,
1831                        jx,
1832                        jx + 1,
1833                        quant,
1834                        quant_offset,
1835                    )));
1836                }
1837            }
1838            ConditionKind::PathActive { node1, node2 } => {
1839                self.add_scale_path_active_constraint(optimizer, node_offsets, node1, node2);
1840            }
1841            ConditionKind::PathAngleFixed {
1842                node1,
1843                node2,
1844                angle,
1845            } => {
1846                self.add_scale_path_active_constraint(optimizer, node_offsets, node1, node2);
1847                let (Some(ix), Some(jx)) = (
1848                    Self::scale_offset(node_offsets, node1),
1849                    Self::scale_offset(node_offsets, node2),
1850                ) else {
1851                    return;
1852                };
1853                optimizer.add_linear_equality(Box::new(nlco::PathAngleFn1::new(
1854                    ix,
1855                    ix + 1,
1856                    jx,
1857                    jx + 1,
1858                    angle,
1859                )));
1860            }
1861            ConditionKind::PathAngleQuant {
1862                node1,
1863                node2,
1864                quant,
1865                quant_offset,
1866            } => {
1867                self.add_scale_path_active_constraint(optimizer, node_offsets, node1, node2);
1868                let (Some(ix), Some(jx)) = (
1869                    Self::scale_offset(node_offsets, node1),
1870                    Self::scale_offset(node_offsets, node2),
1871                ) else {
1872                    return;
1873                };
1874                optimizer.add_nonlinear_equality(Box::new(nlco::QuantizeAngleFn1::new(
1875                    ix,
1876                    ix + 1,
1877                    jx,
1878                    jx + 1,
1879                    quant,
1880                    quant_offset,
1881                )));
1882            }
1883        }
1884    }
1885
1886    fn moving_nodes_for_edge_optimizer(&self) -> Vec<usize> {
1887        self.owned_nodes
1888            .iter()
1889            .copied()
1890            .filter(|id| {
1891                let node = &self.nodes[id - 1];
1892                node.is_leaf && !node.is_pinned
1893            })
1894            .collect()
1895    }
1896
1897    fn edge_has_length_fixed_condition(&self, edge: usize) -> bool {
1898        self.conditions.iter().any(|condition| {
1899            matches!(condition.kind, ConditionKind::EdgeLengthFixed { edge: e } if e == edge)
1900        })
1901    }
1902
1903    fn stretchy_edges_for_edge_optimizer(&self) -> Vec<usize> {
1904        self.owned_edges
1905            .iter()
1906            .copied()
1907            .filter(|id| {
1908                let edge = &self.edges[id - 1];
1909                !edge.is_pinned && !self.edge_has_length_fixed_condition(*id)
1910            })
1911            .collect()
1912    }
1913
1914    fn edge_lookup(&self, edge_ids: &[usize]) -> Vec<bool> {
1915        let mut lookup = vec![false; self.edges.len() + 1];
1916        for id in edge_ids {
1917            lookup[*id] = true;
1918        }
1919        lookup
1920    }
1921
1922    fn node_loc(&self, node: usize) -> Point {
1923        self.nodes[node - 1].loc
1924    }
1925
1926    fn edge_fix_var_lengths(&self, path: &Path, stretchy_lookup: &[bool]) -> (TmFloat, TmFloat) {
1927        let mut lfix = 0.0;
1928        let mut lvar = 0.0;
1929        for edge_id in &path.edges {
1930            let edge = &self.edges[*edge_id - 1];
1931            let temp = edge.length * self.scale;
1932            if stretchy_lookup[*edge_id] {
1933                lfix += temp;
1934                lvar += temp;
1935            } else {
1936                lfix += (1.0 + edge.strain) * temp;
1937            }
1938        }
1939        (lfix, lvar)
1940    }
1941
1942    fn add_edge_path_constraint(
1943        &self,
1944        optimizer: &mut nlco::NlcoAlm,
1945        node_offsets: &[Option<usize>],
1946        stretchy_lookup: &[bool],
1947        path: &Path,
1948        equality: bool,
1949    ) {
1950        let node1 = path.nodes[0];
1951        let node2 = *path.nodes.last().unwrap();
1952        let ix = Self::scale_offset(node_offsets, node1);
1953        let jx = Self::scale_offset(node_offsets, node2);
1954        let (lfix, lvar) = self.edge_fix_var_lengths(path, stretchy_lookup);
1955        let constraint: Option<Box<dyn nlco::DifferentiableFn>> = match (ix, jx) {
1956            (Some(ix), Some(jx)) => Some(Box::new(nlco::StrainPathFn1::new(
1957                ix,
1958                ix + 1,
1959                jx,
1960                jx + 1,
1961                lfix,
1962                lvar,
1963            ))),
1964            (Some(ix), None) => {
1965                let loc = self.node_loc(node2);
1966                Some(Box::new(nlco::StrainPathFn2::new(
1967                    ix,
1968                    ix + 1,
1969                    loc.x,
1970                    loc.y,
1971                    lfix,
1972                    lvar,
1973                )))
1974            }
1975            (None, Some(jx)) => {
1976                let loc = self.node_loc(node1);
1977                Some(Box::new(nlco::StrainPathFn2::new(
1978                    jx,
1979                    jx + 1,
1980                    loc.x,
1981                    loc.y,
1982                    lfix,
1983                    lvar,
1984                )))
1985            }
1986            (None, None) if lvar != 0.0 => {
1987                let loc1 = self.node_loc(node1);
1988                let loc2 = self.node_loc(node2);
1989                Some(Box::new(nlco::StrainPathFn3::new(
1990                    loc1.x, loc1.y, loc2.x, loc2.y, lfix, lvar,
1991                )))
1992            }
1993            (None, None) => None,
1994        };
1995        if let Some(constraint) = constraint {
1996            if equality {
1997                optimizer.add_nonlinear_equality(constraint);
1998            } else {
1999                optimizer.add_nonlinear_inequality(constraint);
2000            }
2001        }
2002    }
2003
2004    fn add_edge_path_active_constraint(
2005        &self,
2006        optimizer: &mut nlco::NlcoAlm,
2007        node_offsets: &[Option<usize>],
2008        stretchy_lookup: &[bool],
2009        node1: usize,
2010        node2: usize,
2011    ) {
2012        if let Some(path) = self.find_leaf_path_between(node1, node2) {
2013            self.add_edge_path_constraint(optimizer, node_offsets, stretchy_lookup, path, true);
2014        }
2015    }
2016
2017    fn add_edge_angle_constraints(
2018        &self,
2019        optimizer: &mut nlco::NlcoAlm,
2020        node_offsets: &[Option<usize>],
2021        node1: usize,
2022        node2: usize,
2023        fixed_angle: Option<TmFloat>,
2024        quant: Option<(usize, TmFloat)>,
2025    ) {
2026        let ix = Self::scale_offset(node_offsets, node1);
2027        let jx = Self::scale_offset(node_offsets, node2);
2028        if let Some(angle) = fixed_angle {
2029            match (ix, jx) {
2030                (Some(ix), Some(jx)) => optimizer.add_nonlinear_equality(Box::new(
2031                    nlco::PathAngleFn1::new(ix, ix + 1, jx, jx + 1, angle),
2032                )),
2033                (Some(ix), None) => {
2034                    let loc = self.node_loc(node2);
2035                    optimizer.add_nonlinear_equality(Box::new(nlco::PathAngleFn2::new(
2036                        ix,
2037                        ix + 1,
2038                        loc.x,
2039                        loc.y,
2040                        angle,
2041                    )));
2042                }
2043                (None, Some(jx)) => {
2044                    let loc = self.node_loc(node1);
2045                    optimizer.add_nonlinear_equality(Box::new(nlco::PathAngleFn2::new(
2046                        jx,
2047                        jx + 1,
2048                        loc.x,
2049                        loc.y,
2050                        angle,
2051                    )));
2052                }
2053                (None, None) => {}
2054            }
2055        }
2056        if let Some((quant, quant_offset)) = quant {
2057            match (ix, jx) {
2058                (Some(ix), Some(jx)) => optimizer.add_nonlinear_equality(Box::new(
2059                    nlco::QuantizeAngleFn1::new(ix, ix + 1, jx, jx + 1, quant, quant_offset),
2060                )),
2061                (Some(ix), None) => {
2062                    let loc = self.node_loc(node2);
2063                    optimizer.add_nonlinear_equality(Box::new(nlco::QuantizeAngleFn2::new(
2064                        ix,
2065                        ix + 1,
2066                        loc.x,
2067                        loc.y,
2068                        quant,
2069                        quant_offset,
2070                    )));
2071                }
2072                (None, Some(jx)) => {
2073                    let loc = self.node_loc(node1);
2074                    optimizer.add_nonlinear_equality(Box::new(nlco::QuantizeAngleFn2::new(
2075                        jx,
2076                        jx + 1,
2077                        loc.x,
2078                        loc.y,
2079                        quant,
2080                        quant_offset,
2081                    )));
2082                }
2083                (None, None) => {}
2084            }
2085        }
2086    }
2087
2088    fn add_pair_constraints(
2089        &self,
2090        optimizer: &mut nlco::NlcoAlm,
2091        node_offsets: &[Option<usize>],
2092        node1: usize,
2093        node2: usize,
2094    ) {
2095        if !self.has_symmetry {
2096            return;
2097        }
2098        let ix = Self::scale_offset(node_offsets, node1);
2099        let jx = Self::scale_offset(node_offsets, node2);
2100        match (ix, jx) {
2101            (Some(ix), Some(jx)) => {
2102                optimizer.add_linear_equality(Box::new(nlco::PairFn1A::new(
2103                    ix,
2104                    ix + 1,
2105                    jx,
2106                    jx + 1,
2107                    self.sym_angle,
2108                )));
2109                optimizer.add_linear_equality(Box::new(nlco::PairFn1B::new(
2110                    ix,
2111                    ix + 1,
2112                    jx,
2113                    jx + 1,
2114                    self.sym_loc.x,
2115                    self.sym_loc.y,
2116                    self.sym_angle,
2117                )));
2118            }
2119            (Some(ix), None) => {
2120                let loc = self.node_loc(node2);
2121                optimizer.add_linear_equality(Box::new(nlco::PairFn2A::new(
2122                    ix,
2123                    ix + 1,
2124                    loc.x,
2125                    loc.y,
2126                    self.sym_angle,
2127                )));
2128                optimizer.add_linear_equality(Box::new(nlco::PairFn2B::new(
2129                    ix,
2130                    ix + 1,
2131                    loc.x,
2132                    loc.y,
2133                    self.sym_loc.x,
2134                    self.sym_loc.y,
2135                    self.sym_angle,
2136                )));
2137            }
2138            (None, Some(jx)) => {
2139                let loc = self.node_loc(node1);
2140                optimizer.add_linear_equality(Box::new(nlco::PairFn2A::new(
2141                    jx,
2142                    jx + 1,
2143                    loc.x,
2144                    loc.y,
2145                    self.sym_angle,
2146                )));
2147                optimizer.add_linear_equality(Box::new(nlco::PairFn2B::new(
2148                    jx,
2149                    jx + 1,
2150                    loc.x,
2151                    loc.y,
2152                    self.sym_loc.x,
2153                    self.sym_loc.y,
2154                    self.sym_angle,
2155                )));
2156            }
2157            (None, None) => {}
2158        }
2159    }
2160
2161    fn add_collinear_constraints(
2162        &self,
2163        optimizer: &mut nlco::NlcoAlm,
2164        node_offsets: &[Option<usize>],
2165        node1: usize,
2166        node2: usize,
2167        node3: usize,
2168    ) {
2169        let ix = Self::scale_offset(node_offsets, node1);
2170        let jx = Self::scale_offset(node_offsets, node2);
2171        let kx = Self::scale_offset(node_offsets, node3);
2172        match (ix, jx, kx) {
2173            (Some(ix), Some(jx), Some(kx)) => {
2174                optimizer.add_nonlinear_equality(Box::new(nlco::CollinearFn1::new(
2175                    ix,
2176                    ix + 1,
2177                    jx,
2178                    jx + 1,
2179                    kx,
2180                    kx + 1,
2181                )));
2182            }
2183            (Some(ix), Some(jx), None) => {
2184                let loc = self.node_loc(node3);
2185                optimizer.add_nonlinear_equality(Box::new(nlco::CollinearFn2::new(
2186                    ix,
2187                    ix + 1,
2188                    jx,
2189                    jx + 1,
2190                    loc.x,
2191                    loc.y,
2192                )));
2193            }
2194            (Some(ix), None, Some(kx)) => {
2195                let loc = self.node_loc(node2);
2196                optimizer.add_nonlinear_equality(Box::new(nlco::CollinearFn2::new(
2197                    ix,
2198                    ix + 1,
2199                    kx,
2200                    kx + 1,
2201                    loc.x,
2202                    loc.y,
2203                )));
2204            }
2205            (None, Some(jx), Some(kx)) => {
2206                let loc = self.node_loc(node1);
2207                optimizer.add_nonlinear_equality(Box::new(nlco::CollinearFn2::new(
2208                    jx,
2209                    jx + 1,
2210                    kx,
2211                    kx + 1,
2212                    loc.x,
2213                    loc.y,
2214                )));
2215            }
2216            (Some(ix), None, None) => {
2217                let loc2 = self.node_loc(node2);
2218                let loc3 = self.node_loc(node3);
2219                optimizer.add_nonlinear_equality(Box::new(nlco::CollinearFn3::new(
2220                    ix,
2221                    ix + 1,
2222                    loc2.x,
2223                    loc2.y,
2224                    loc3.x,
2225                    loc3.y,
2226                )));
2227            }
2228            (None, Some(jx), None) => {
2229                let loc1 = self.node_loc(node1);
2230                let loc3 = self.node_loc(node3);
2231                optimizer.add_nonlinear_equality(Box::new(nlco::CollinearFn3::new(
2232                    jx,
2233                    jx + 1,
2234                    loc1.x,
2235                    loc1.y,
2236                    loc3.x,
2237                    loc3.y,
2238                )));
2239            }
2240            (None, None, Some(kx)) => {
2241                let loc1 = self.node_loc(node1);
2242                let loc2 = self.node_loc(node2);
2243                optimizer.add_nonlinear_equality(Box::new(nlco::CollinearFn3::new(
2244                    kx,
2245                    kx + 1,
2246                    loc1.x,
2247                    loc1.y,
2248                    loc2.x,
2249                    loc2.y,
2250                )));
2251            }
2252            (None, None, None) => {}
2253        }
2254    }
2255
2256    fn add_edge_condition_constraints(
2257        &self,
2258        optimizer: &mut nlco::NlcoAlm,
2259        node_offsets: &[Option<usize>],
2260        stretchy_lookup: &[bool],
2261        kind: &ConditionKind,
2262    ) {
2263        match *kind {
2264            ConditionKind::NodeCombo {
2265                node,
2266                to_symmetry_line,
2267                to_paper_edge,
2268                to_paper_corner,
2269                x_fixed,
2270                x_fix_value,
2271                y_fixed,
2272                y_fix_value,
2273            } => {
2274                let Some(ix) = Self::scale_offset(node_offsets, node) else {
2275                    return;
2276                };
2277                let iy = ix + 1;
2278                if self.has_symmetry && to_symmetry_line {
2279                    optimizer.add_linear_equality(Box::new(nlco::StickToLineFn::new(
2280                        ix,
2281                        iy,
2282                        self.sym_loc.x,
2283                        self.sym_loc.y,
2284                        self.sym_angle,
2285                    )));
2286                }
2287                if to_paper_edge {
2288                    optimizer.add_nonlinear_equality(Box::new(nlco::StickToEdgeFn::new(
2289                        ix,
2290                        iy,
2291                        self.paper_width,
2292                        self.paper_height,
2293                    )));
2294                }
2295                if to_paper_corner {
2296                    optimizer.add_nonlinear_equality(Box::new(nlco::CornerFn::new(
2297                        ix,
2298                        self.paper_width,
2299                    )));
2300                    optimizer.add_nonlinear_equality(Box::new(nlco::CornerFn::new(
2301                        iy,
2302                        self.paper_height,
2303                    )));
2304                }
2305                if x_fixed {
2306                    optimizer.add_linear_equality(Box::new(nlco::OneVarFn::new(
2307                        ix,
2308                        -1.0,
2309                        x_fix_value,
2310                    )));
2311                }
2312                if y_fixed {
2313                    optimizer.add_linear_equality(Box::new(nlco::OneVarFn::new(
2314                        iy,
2315                        -1.0,
2316                        y_fix_value,
2317                    )));
2318                }
2319            }
2320            ConditionKind::NodeFixed {
2321                node,
2322                x_fixed,
2323                y_fixed,
2324                x_fix_value,
2325                y_fix_value,
2326            } => {
2327                let Some(ix) = Self::scale_offset(node_offsets, node) else {
2328                    return;
2329                };
2330                if x_fixed {
2331                    optimizer.add_linear_equality(Box::new(nlco::OneVarFn::new(
2332                        ix,
2333                        -1.0,
2334                        x_fix_value,
2335                    )));
2336                }
2337                if y_fixed {
2338                    optimizer.add_linear_equality(Box::new(nlco::OneVarFn::new(
2339                        ix + 1,
2340                        -1.0,
2341                        y_fix_value,
2342                    )));
2343                }
2344            }
2345            ConditionKind::NodeOnCorner { node } => {
2346                if let Some(ix) = Self::scale_offset(node_offsets, node) {
2347                    optimizer.add_nonlinear_equality(Box::new(nlco::CornerFn::new(
2348                        ix,
2349                        self.paper_width,
2350                    )));
2351                    optimizer.add_nonlinear_equality(Box::new(nlco::CornerFn::new(
2352                        ix + 1,
2353                        self.paper_height,
2354                    )));
2355                }
2356            }
2357            ConditionKind::NodeOnEdge { node } => {
2358                if let Some(ix) = Self::scale_offset(node_offsets, node) {
2359                    optimizer.add_nonlinear_equality(Box::new(nlco::StickToEdgeFn::new(
2360                        ix,
2361                        ix + 1,
2362                        self.paper_width,
2363                        self.paper_height,
2364                    )));
2365                }
2366            }
2367            ConditionKind::NodeSymmetric { node } => {
2368                if !self.has_symmetry {
2369                    return;
2370                }
2371                if let Some(ix) = Self::scale_offset(node_offsets, node) {
2372                    optimizer.add_linear_equality(Box::new(nlco::StickToLineFn::new(
2373                        ix,
2374                        ix + 1,
2375                        self.sym_loc.x,
2376                        self.sym_loc.y,
2377                        self.sym_angle,
2378                    )));
2379                }
2380            }
2381            ConditionKind::NodesPaired { node1, node2 } => {
2382                self.add_pair_constraints(optimizer, node_offsets, node1, node2);
2383            }
2384            ConditionKind::NodesCollinear {
2385                node1,
2386                node2,
2387                node3,
2388            } => self.add_collinear_constraints(optimizer, node_offsets, node1, node2, node3),
2389            ConditionKind::EdgeLengthFixed { edge } => {
2390                if stretchy_lookup[edge] {
2391                    optimizer.add_linear_equality(Box::new(nlco::OneVarFn::new(0, 1.0, 0.0)));
2392                }
2393            }
2394            ConditionKind::EdgesSameStrain { .. } => {}
2395            ConditionKind::PathCombo {
2396                node1,
2397                node2,
2398                is_angle_fixed,
2399                angle,
2400                is_angle_quant,
2401                quant,
2402                quant_offset,
2403            } => {
2404                self.add_edge_path_active_constraint(
2405                    optimizer,
2406                    node_offsets,
2407                    stretchy_lookup,
2408                    node1,
2409                    node2,
2410                );
2411                self.add_edge_angle_constraints(
2412                    optimizer,
2413                    node_offsets,
2414                    node1,
2415                    node2,
2416                    is_angle_fixed.then_some(angle),
2417                    is_angle_quant.then_some((quant, quant_offset)),
2418                );
2419            }
2420            ConditionKind::PathActive { node1, node2 } => {
2421                self.add_edge_path_active_constraint(
2422                    optimizer,
2423                    node_offsets,
2424                    stretchy_lookup,
2425                    node1,
2426                    node2,
2427                );
2428            }
2429            ConditionKind::PathAngleFixed {
2430                node1,
2431                node2,
2432                angle,
2433            } => {
2434                self.add_edge_path_active_constraint(
2435                    optimizer,
2436                    node_offsets,
2437                    stretchy_lookup,
2438                    node1,
2439                    node2,
2440                );
2441                self.add_edge_angle_constraints(
2442                    optimizer,
2443                    node_offsets,
2444                    node1,
2445                    node2,
2446                    Some(angle),
2447                    None,
2448                );
2449            }
2450            ConditionKind::PathAngleQuant {
2451                node1,
2452                node2,
2453                quant,
2454                quant_offset,
2455            } => {
2456                self.add_edge_path_active_constraint(
2457                    optimizer,
2458                    node_offsets,
2459                    stretchy_lookup,
2460                    node1,
2461                    node2,
2462                );
2463                self.add_edge_angle_constraints(
2464                    optimizer,
2465                    node_offsets,
2466                    node1,
2467                    node2,
2468                    None,
2469                    Some((quant, quant_offset)),
2470                );
2471            }
2472        }
2473    }
2474
2475    fn moving_nodes_for_strain_optimizer(&self) -> Vec<usize> {
2476        self.owned_nodes
2477            .iter()
2478            .copied()
2479            .filter(|id| self.nodes[id - 1].is_leaf)
2480            .collect()
2481    }
2482
2483    fn strain_fix_var_lengths(
2484        &self,
2485        path: &Path,
2486        edge_offsets: &[Option<usize>],
2487    ) -> (TmFloat, Vec<usize>, Vec<TmFloat>) {
2488        let mut lfix = 0.0;
2489        let mut vi = Vec::new();
2490        let mut vf = Vec::new();
2491        for edge_id in &path.edges {
2492            let edge = &self.edges[*edge_id - 1];
2493            if let Some(offset) = edge_offsets[*edge_id] {
2494                vi.push(offset);
2495                let scaled_length = edge.length * self.scale;
2496                vf.push(scaled_length);
2497                lfix += scaled_length;
2498            } else {
2499                lfix += edge.strained_length() * self.scale;
2500            }
2501        }
2502        (lfix, vi, vf)
2503    }
2504
2505    fn add_strain_path_constraint(
2506        &self,
2507        optimizer: &mut nlco::NlcoAlm,
2508        node_offsets: &[Option<usize>],
2509        edge_offsets: &[Option<usize>],
2510        path: &Path,
2511        equality: bool,
2512    ) {
2513        let node1 = path.nodes[0];
2514        let node2 = *path.nodes.last().unwrap();
2515        let ix = Self::scale_offset(node_offsets, node1);
2516        let jx = Self::scale_offset(node_offsets, node2);
2517        let (lfix, vi, vf) = self.strain_fix_var_lengths(path, edge_offsets);
2518        let constraint: Option<Box<dyn nlco::DifferentiableFn>> = match (ix, jx) {
2519            (Some(ix), Some(jx)) => Some(Box::new(nlco::MultiStrainPathFn1::new(
2520                ix,
2521                ix + 1,
2522                jx,
2523                jx + 1,
2524                lfix,
2525                vi,
2526                vf,
2527            ))),
2528            (Some(ix), None) => {
2529                let loc = self.node_loc(node2);
2530                Some(Box::new(nlco::MultiStrainPathFn2::new(
2531                    ix,
2532                    ix + 1,
2533                    loc.x,
2534                    loc.y,
2535                    lfix,
2536                    vi,
2537                    vf,
2538                )))
2539            }
2540            (None, Some(jx)) => {
2541                let loc = self.node_loc(node1);
2542                Some(Box::new(nlco::MultiStrainPathFn2::new(
2543                    jx,
2544                    jx + 1,
2545                    loc.x,
2546                    loc.y,
2547                    lfix,
2548                    vi,
2549                    vf,
2550                )))
2551            }
2552            (None, None) if !vi.is_empty() => {
2553                let loc1 = self.node_loc(node1);
2554                let loc2 = self.node_loc(node2);
2555                Some(Box::new(nlco::MultiStrainPathFn3::new(
2556                    loc1.x, loc1.y, loc2.x, loc2.y, lfix, vi, vf,
2557                )))
2558            }
2559            (None, None) => None,
2560        };
2561        if let Some(constraint) = constraint {
2562            if equality {
2563                optimizer.add_nonlinear_equality(constraint);
2564            } else {
2565                optimizer.add_nonlinear_inequality(constraint);
2566            }
2567        }
2568    }
2569
2570    fn add_strain_path_active_constraint(
2571        &self,
2572        optimizer: &mut nlco::NlcoAlm,
2573        node_offsets: &[Option<usize>],
2574        edge_offsets: &[Option<usize>],
2575        node1: usize,
2576        node2: usize,
2577    ) {
2578        if let Some(path) = self.find_leaf_path_between(node1, node2) {
2579            self.add_strain_path_constraint(optimizer, node_offsets, edge_offsets, path, true);
2580        }
2581    }
2582
2583    fn add_strain_condition_constraints(
2584        &self,
2585        optimizer: &mut nlco::NlcoAlm,
2586        node_offsets: &[Option<usize>],
2587        edge_offsets: &[Option<usize>],
2588        kind: &ConditionKind,
2589    ) {
2590        match *kind {
2591            ConditionKind::EdgeLengthFixed { edge } => {
2592                if let Some(offset) = edge_offsets[edge] {
2593                    optimizer.add_linear_equality(Box::new(nlco::OneVarFn::new(offset, 1.0, 0.0)));
2594                }
2595            }
2596            ConditionKind::EdgesSameStrain { edge1, edge2 } => {
2597                if let (Some(offset1), Some(offset2)) = (edge_offsets[edge1], edge_offsets[edge2]) {
2598                    optimizer.add_linear_equality(Box::new(nlco::TwoVarFn::new(
2599                        offset1, 1.0, offset2, -1.0, 0.0,
2600                    )));
2601                }
2602            }
2603            ConditionKind::PathCombo {
2604                node1,
2605                node2,
2606                is_angle_fixed,
2607                angle,
2608                is_angle_quant,
2609                quant,
2610                quant_offset,
2611            } => {
2612                self.add_strain_path_active_constraint(
2613                    optimizer,
2614                    node_offsets,
2615                    edge_offsets,
2616                    node1,
2617                    node2,
2618                );
2619                self.add_edge_angle_constraints(
2620                    optimizer,
2621                    node_offsets,
2622                    node1,
2623                    node2,
2624                    is_angle_fixed.then_some(angle),
2625                    is_angle_quant.then_some((quant, quant_offset)),
2626                );
2627            }
2628            ConditionKind::PathActive { node1, node2 } => {
2629                self.add_strain_path_active_constraint(
2630                    optimizer,
2631                    node_offsets,
2632                    edge_offsets,
2633                    node1,
2634                    node2,
2635                );
2636            }
2637            ConditionKind::PathAngleFixed {
2638                node1,
2639                node2,
2640                angle,
2641            } => {
2642                self.add_strain_path_active_constraint(
2643                    optimizer,
2644                    node_offsets,
2645                    edge_offsets,
2646                    node1,
2647                    node2,
2648                );
2649                self.add_edge_angle_constraints(
2650                    optimizer,
2651                    node_offsets,
2652                    node1,
2653                    node2,
2654                    Some(angle),
2655                    None,
2656                );
2657            }
2658            ConditionKind::PathAngleQuant {
2659                node1,
2660                node2,
2661                quant,
2662                quant_offset,
2663            } => {
2664                self.add_strain_path_active_constraint(
2665                    optimizer,
2666                    node_offsets,
2667                    edge_offsets,
2668                    node1,
2669                    node2,
2670                );
2671                self.add_edge_angle_constraints(
2672                    optimizer,
2673                    node_offsets,
2674                    node1,
2675                    node2,
2676                    None,
2677                    Some((quant, quant_offset)),
2678                );
2679            }
2680            _ => self.add_edge_condition_constraints(
2681                optimizer,
2682                node_offsets,
2683                &self.edge_lookup(&[]),
2684                kind,
2685            ),
2686        }
2687    }
2688
2689    fn build_polys_from_paths(
2690        &mut self,
2691        path_list: &[usize],
2692        border_nodes: &[usize],
2693        owner: OwnerRef,
2694    ) -> Result<()> {
2695        let mut polygon_paths = Vec::new();
2696        for path_id in path_list.iter().copied() {
2697            if !self.paths[path_id - 1].is_polygon {
2698                continue;
2699            }
2700            for existing_path in polygon_paths.iter().copied() {
2701                if self.paths_intersect_interior(path_id, existing_path) {
2702                    self.paths[path_id - 1].is_polygon = false;
2703                    break;
2704                }
2705            }
2706            if self.paths[path_id - 1].is_polygon {
2707                polygon_paths.push(path_id);
2708            }
2709        }
2710
2711        if polygon_paths.is_empty() || border_nodes.is_empty() {
2712            return Ok(());
2713        }
2714
2715        let centroid = self.node_centroid(border_nodes);
2716        for path_id in polygon_paths.iter().copied() {
2717            if self.can_start_poly_fwd(path_id, centroid) {
2718                self.build_poly_ring(path_id, true, owner.clone())?;
2719            }
2720            if self.can_start_poly_bkd(path_id, centroid) {
2721                self.build_poly_ring(path_id, false, owner.clone())?;
2722            }
2723        }
2724
2725        let owned_polys = self.owned_polys_for_owner(&owner);
2726        for poly_id in owned_polys {
2727            if self.polys[poly_id - 1].cross_paths.is_empty() {
2728                self.calc_poly_cross_paths(poly_id);
2729            }
2730        }
2731        Ok(())
2732    }
2733
2734    fn owned_polys_for_owner(&self, owner: &OwnerRef) -> Vec<usize> {
2735        match *owner {
2736            OwnerRef::Tree => self.owned_polys.clone(),
2737            OwnerRef::Poly(poly_id) => self
2738                .polys
2739                .get(poly_id.saturating_sub(1))
2740                .map(|poly| poly.owned_polys.clone())
2741                .unwrap_or_default(),
2742            _ => Vec::new(),
2743        }
2744    }
2745
2746    fn can_start_poly_fwd(&self, path_id: usize, centroid: Point) -> bool {
2747        let path = &self.paths[path_id - 1];
2748        if path.fwd_poly.is_some() {
2749            return false;
2750        }
2751        if !path.is_border {
2752            return true;
2753        }
2754        let (Some(front), Some(back)) = (path.nodes.first(), path.nodes.last()) else {
2755            return false;
2756        };
2757        are_ccw(
2758            self.nodes[*front - 1].loc,
2759            self.nodes[*back - 1].loc,
2760            centroid,
2761        )
2762    }
2763
2764    fn can_start_poly_bkd(&self, path_id: usize, centroid: Point) -> bool {
2765        let path = &self.paths[path_id - 1];
2766        if path.bkd_poly.is_some() {
2767            return false;
2768        }
2769        if !path.is_border {
2770            return true;
2771        }
2772        let (Some(front), Some(back)) = (path.nodes.first(), path.nodes.last()) else {
2773            return false;
2774        };
2775        are_cw(
2776            self.nodes[*front - 1].loc,
2777            self.nodes[*back - 1].loc,
2778            centroid,
2779        )
2780    }
2781
2782    fn build_poly_ring(&mut self, path_id: usize, fwd: bool, owner: OwnerRef) -> Result<()> {
2783        let poly_id = self.create_poly(owner);
2784        if fwd {
2785            self.paths[path_id - 1].fwd_poly = Some(poly_id);
2786        } else {
2787            self.paths[path_id - 1].bkd_poly = Some(poly_id);
2788        }
2789
2790        let path = &self.paths[path_id - 1];
2791        let first_node = if fwd {
2792            path.nodes[0]
2793        } else {
2794            *path
2795                .nodes
2796                .last()
2797                .ok_or(TreeError::InvalidOperation("polygon path has no nodes"))?
2798        };
2799        let mut this_node = if fwd {
2800            *path
2801                .nodes
2802                .last()
2803                .ok_or(TreeError::InvalidOperation("polygon path has no nodes"))?
2804        } else {
2805            path.nodes[0]
2806        };
2807        let mut this_path = path_id;
2808        let mut ring_nodes = vec![first_node];
2809        let mut ring_paths = vec![this_path];
2810
2811        let mut too_many = 0;
2812        loop {
2813            let (next_path, next_node) = self.next_polygon_path_and_node(this_path, this_node)?;
2814            ring_nodes.push(this_node);
2815            ring_paths.push(next_path);
2816            if self.paths[next_path - 1].nodes.first().copied() == Some(this_node) {
2817                self.paths[next_path - 1].fwd_poly = Some(poly_id);
2818            } else {
2819                self.paths[next_path - 1].bkd_poly = Some(poly_id);
2820            }
2821            this_path = next_path;
2822            this_node = next_node;
2823            too_many += 1;
2824            if next_node == first_node {
2825                break;
2826            }
2827            if too_many >= 100 {
2828                return Err(TreeError::InvalidOperation(
2829                    "polygon ring walk exceeded TreeMaker guard",
2830                ));
2831            }
2832        }
2833
2834        self.polys[poly_id - 1].ring_nodes = ring_nodes;
2835        self.polys[poly_id - 1].ring_paths = ring_paths;
2836        self.calc_poly_contents(poly_id);
2837        Ok(())
2838    }
2839
2840    fn create_poly(&mut self, owner: OwnerRef) -> usize {
2841        let index = self.polys.len() + 1;
2842        self.polys.push(Poly {
2843            index,
2844            centroid: Point { x: 0.0, y: 0.0 },
2845            is_sub_poly: matches!(owner, OwnerRef::Poly(_)),
2846            ring_nodes: Vec::new(),
2847            ring_paths: Vec::new(),
2848            cross_paths: Vec::new(),
2849            inset_nodes: Vec::new(),
2850            spoke_paths: Vec::new(),
2851            ridge_path: None,
2852            node_locs: Vec::new(),
2853            local_root_vertices: Vec::new(),
2854            local_root_creases: Vec::new(),
2855            owned_nodes: Vec::new(),
2856            owned_paths: Vec::new(),
2857            owned_polys: Vec::new(),
2858            owned_creases: Vec::new(),
2859            owned_facets: Vec::new(),
2860            owner: owner.clone(),
2861        });
2862        match owner {
2863            OwnerRef::Tree => self.owned_polys.push(index),
2864            OwnerRef::Poly(poly_id) => {
2865                if let Some(poly) = self.polys.get_mut(poly_id.saturating_sub(1)) {
2866                    poly.owned_polys.push(index);
2867                }
2868            }
2869            _ => {}
2870        }
2871        index
2872    }
2873
2874    fn next_polygon_path_and_node(
2875        &self,
2876        this_path: usize,
2877        this_node: usize,
2878    ) -> Result<(usize, usize)> {
2879        let path = &self.paths[this_path - 1];
2880        let mut that_node = path.nodes[0];
2881        if that_node == this_node {
2882            that_node = *path
2883                .nodes
2884                .last()
2885                .ok_or(TreeError::InvalidOperation("polygon path has no nodes"))?;
2886        }
2887        let this_angle = angle(point_sub(
2888            self.nodes[that_node - 1].loc,
2889            self.nodes[this_node - 1].loc,
2890        ));
2891
2892        let mut delta = TWO_PI;
2893        let mut next_path = None;
2894        let mut next_node = None;
2895        for candidate_path in self.nodes[this_node - 1].leaf_paths.iter().copied() {
2896            if candidate_path == this_path || !self.paths[candidate_path - 1].is_polygon {
2897                continue;
2898            }
2899            let candidate = &self.paths[candidate_path - 1];
2900            let mut candidate_node = candidate.nodes[0];
2901            if candidate_node == this_node {
2902                candidate_node = *candidate
2903                    .nodes
2904                    .last()
2905                    .ok_or(TreeError::InvalidOperation("polygon path has no nodes"))?;
2906            }
2907            let candidate_angle = angle(point_sub(
2908                self.nodes[candidate_node - 1].loc,
2909                self.nodes[this_node - 1].loc,
2910            ));
2911            let mut new_delta = this_angle - candidate_angle;
2912            while new_delta < 0.0 {
2913                new_delta += TWO_PI;
2914            }
2915            while new_delta >= TWO_PI {
2916                new_delta -= TWO_PI;
2917            }
2918            if new_delta < delta {
2919                delta = new_delta;
2920                next_path = Some(candidate_path);
2921                next_node = Some(candidate_node);
2922            }
2923        }
2924
2925        match (next_path, next_node) {
2926            (Some(path), Some(node)) => Ok((path, node)),
2927            _ => Err(TreeError::InvalidOperation(
2928                "polygon path walk could not advance",
2929            )),
2930        }
2931    }
2932
2933    fn calc_poly_contents(&mut self, poly_id: usize) {
2934        let ring_nodes = self.polys[poly_id - 1].ring_nodes.clone();
2935        let mut centroid = Point { x: 0.0, y: 0.0 };
2936        let mut node_locs = Vec::with_capacity(ring_nodes.len());
2937        for node_id in ring_nodes {
2938            let loc = self.nodes[node_id - 1].loc;
2939            node_locs.push(loc);
2940            centroid.x += loc.x;
2941            centroid.y += loc.y;
2942        }
2943        if !node_locs.is_empty() {
2944            centroid.x /= node_locs.len() as TmFloat;
2945            centroid.y /= node_locs.len() as TmFloat;
2946        }
2947        let poly = &mut self.polys[poly_id - 1];
2948        poly.node_locs = node_locs;
2949        poly.centroid = centroid;
2950    }
2951
2952    fn calc_poly_cross_paths(&mut self, poly_id: usize) {
2953        let ring_nodes = self.polys[poly_id - 1].ring_nodes.clone();
2954        let owner_paths = match self.polys[poly_id - 1].owner {
2955            OwnerRef::Tree => self.owned_paths.clone(),
2956            OwnerRef::Poly(owner_id) => self
2957                .polys
2958                .get(owner_id.saturating_sub(1))
2959                .map(|poly| poly.owned_paths.clone())
2960                .unwrap_or_default(),
2961            _ => Vec::new(),
2962        };
2963        let mut cross_paths = Vec::new();
2964        let nn = ring_nodes.len();
2965        for i in 2..nn {
2966            for j in 0..i - 1 {
2967                if i == nn - 1 && j == 0 {
2968                    continue;
2969                }
2970                if let Some(path_id) =
2971                    self.find_any_path_in(&owner_paths, ring_nodes[i], ring_nodes[j])
2972                {
2973                    push_unique(&mut cross_paths, path_id);
2974                }
2975            }
2976        }
2977        self.polys[poly_id - 1].cross_paths = cross_paths;
2978    }
2979
2980    fn build_poly_contents_geometry(&mut self, poly_id: usize) -> Result<()> {
2981        if !self.polys[poly_id - 1].owned_nodes.is_empty() {
2982            return Ok(());
2983        }
2984
2985        let ring_nodes = self.polys[poly_id - 1].ring_nodes.clone();
2986        let nn = ring_nodes.len();
2987        if nn < 3 {
2988            return Err(TreeError::InvalidOperation(
2989                "polygon contents require at least three ring nodes",
2990            ));
2991        }
2992
2993        if nn == 3 {
2994            let p1 = self.nodes[ring_nodes[0] - 1].loc;
2995            let p2 = self.nodes[ring_nodes[1] - 1].loc;
2996            let p3 = self.nodes[ring_nodes[2] - 1].loc;
2997            let node_id = self.create_sub_node(poly_id, incenter(p1, p2, p3));
2998            self.nodes[node_id - 1].is_junction = true;
2999            self.nodes[node_id - 1].elevation =
3000                self.nodes[ring_nodes[0] - 1].elevation + inradius(p1, p2, p3);
3001            self.polys[poly_id - 1].inset_nodes = vec![node_id, node_id, node_id];
3002
3003            for ring_node in ring_nodes {
3004                let path_id = self.create_sub_path(poly_id, ring_node, node_id, false);
3005                self.polys[poly_id - 1].spoke_paths.push(path_id);
3006            }
3007        } else {
3008            self.build_inset_poly_contents(poly_id)?;
3009        }
3010
3011        self.build_poly_creases_and_facets(poly_id)?;
3012        Ok(())
3013    }
3014
3015    fn build_inset_poly_contents(&mut self, poly_id: usize) -> Result<()> {
3016        let ring_nodes = self.polys[poly_id - 1].ring_nodes.clone();
3017        let nn = ring_nodes.len();
3018        let mut r = vec![Point { x: 0.0, y: 0.0 }; nn];
3019        let mut rp = vec![Point { x: 0.0, y: 0.0 }; nn];
3020        let mut rn = vec![Point { x: 0.0, y: 0.0 }; nn];
3021        let mut mr = vec![0.0; nn];
3022
3023        for i in 0..nn {
3024            let ip = (i + nn - 1) % nn;
3025            let inext = (i + 1) % nn;
3026            let nip = self.nodes[ring_nodes[ip] - 1].loc;
3027            let nii = self.nodes[ring_nodes[i] - 1].loc;
3028            let nin = self.nodes[ring_nodes[inext] - 1].loc;
3029            rp[i] = normalize(point_sub(nip, nii));
3030            rn[i] = normalize(point_sub(nin, nii));
3031            let bis = normalize(rotate_ccw90(point_sub(rn[i], rp[i])));
3032            r[i] = point_div(bis, inner(bis, rotate_ccw90(rn[i])));
3033            mr[i] = inner(r[i], rp[i]);
3034        }
3035
3036        let owner_paths = self.owner_paths_for_poly(poly_id);
3037        let h = self.calc_poly_inset_distance(poly_id, &owner_paths, &r, &rn, &mr)?;
3038
3039        let mut inset_nodes = Vec::with_capacity(nn);
3040        for i in 0..nn {
3041            let p = point_add(self.nodes[ring_nodes[i] - 1].loc, point_mul(r[i], h));
3042            inset_nodes.push(self.get_or_make_inset_node(poly_id, p));
3043        }
3044        self.polys[poly_id - 1].inset_nodes = inset_nodes.clone();
3045
3046        let owned_nodes = self.polys[poly_id - 1].owned_nodes.clone();
3047        for node_id in owned_nodes.iter().copied() {
3048            self.nodes[node_id - 1].elevation = self.nodes[ring_nodes[0] - 1].elevation + h;
3049        }
3050
3051        match owned_nodes.len() {
3052            0 => {
3053                return Err(TreeError::InvalidOperation(
3054                    "polygon inset produced no owned nodes",
3055                ));
3056            }
3057            1 | 2 => {
3058                self.create_spoke_paths(poly_id, &ring_nodes, &inset_nodes);
3059                if owned_nodes.len() == 2 {
3060                    let ridge =
3061                        self.create_sub_path(poly_id, owned_nodes[0], owned_nodes[1], false);
3062                    self.polys[poly_id - 1].ridge_path = Some(ridge);
3063                }
3064            }
3065            _ => {
3066                for dij in 1..nn {
3067                    for i in 0..=nn - dij {
3068                        let j = (i + dij) % nn;
3069                        let ni = ring_nodes[i];
3070                        let nj = ring_nodes[j];
3071                        let rni = inset_nodes[i];
3072                        let rnj = inset_nodes[j];
3073                        if rni == rnj || self.find_leaf_path_between_any(rni, rnj).is_some() {
3074                            continue;
3075                        }
3076
3077                        let outset_path = self
3078                            .find_any_path_in(&owner_paths, ni, nj)
3079                            .ok_or(TreeError::InvalidOperation("missing outset path"))?;
3080                        let i_reduction = h * mr[i];
3081                        let j_reduction = h * mr[j];
3082
3083                        let (front, back, front_reduction, back_reduction) =
3084                            if self.paths[outset_path - 1].nodes.first().copied() == Some(ni) {
3085                                (rni, rnj, i_reduction, j_reduction)
3086                            } else {
3087                                (rnj, rni, j_reduction, i_reduction)
3088                            };
3089                        let path_id = self.create_sub_path(poly_id, front, back, true);
3090                        let min_paper_length = self.paths[outset_path - 1].min_paper_length
3091                            - (front_reduction + back_reduction);
3092                        let act_paper_length =
3093                            self.nodes[rni - 1].loc.distance(self.nodes[rnj - 1].loc);
3094                        let outset_active = self.paths[outset_path - 1].is_active;
3095                        let path = &mut self.paths[path_id - 1];
3096                        path.outset_path = Some(outset_path);
3097                        path.front_reduction = front_reduction;
3098                        path.back_reduction = back_reduction;
3099                        path.min_paper_length = min_paper_length;
3100                        path.act_paper_length = act_paper_length;
3101                        path.min_tree_length = min_paper_length / self.scale;
3102                        path.act_tree_length = act_paper_length / self.scale;
3103                        path.is_active =
3104                            outset_active || is_tiny(act_paper_length - min_paper_length);
3105                        path.is_border = dij == 1;
3106                        path.is_polygon = path.is_active || path.is_border;
3107                    }
3108                }
3109
3110                let owned_paths = self.polys[poly_id - 1].owned_paths.clone();
3111                self.build_polys_from_paths(&owned_paths, &inset_nodes, OwnerRef::Poly(poly_id))?;
3112
3113                let owned_polys = self.polys[poly_id - 1].owned_polys.clone();
3114                for sub_poly_id in owned_polys {
3115                    self.build_poly_contents_geometry(sub_poly_id)?;
3116                }
3117
3118                self.create_spoke_paths(poly_id, &ring_nodes, &inset_nodes);
3119            }
3120        }
3121
3122        Ok(())
3123    }
3124
3125    fn calc_poly_inset_distance(
3126        &self,
3127        poly_id: usize,
3128        owner_paths: &[usize],
3129        r: &[Point],
3130        rn: &[Point],
3131        mr: &[TmFloat],
3132    ) -> Result<TmFloat> {
3133        let ring_nodes = &self.polys[poly_id - 1].ring_nodes;
3134        let nn = ring_nodes.len();
3135        let mut h = 1.0e10;
3136
3137        for i in 0..nn - 1 {
3138            for j in i + 1..nn {
3139                if are_parallel(r[i], r[j]) && inner(r[i], r[j]) > 0.0 {
3140                    continue;
3141                }
3142
3143                let ni = self.nodes[ring_nodes[i] - 1].loc;
3144                let nj = self.nodes[ring_nodes[j] - 1].loc;
3145                if j == i + 1 || (i == 0 && j == nn - 1) {
3146                    let Some(bi) = line_intersection_point_exact(ni, r[i], nj, r[j]) else {
3147                        return Err(TreeError::InvalidOperation(
3148                            "adjacent inset bisectors are parallel",
3149                        ));
3150                    };
3151                    let h1 = inner(point_sub(bi, ni), rotate_ccw90(rn[i]));
3152                    if h1 > 0.0 && h > h1 {
3153                        h = h1;
3154                    }
3155                } else {
3156                    let path_id = self
3157                        .find_any_path_in(owner_paths, ring_nodes[i], ring_nodes[j])
3158                        .ok_or(TreeError::InvalidOperation("missing poly cross path"))?;
3159                    let lij = self.paths[path_id - 1].min_paper_length;
3160                    let u = point_sub(ni, nj);
3161                    let v = point_sub(r[i], r[j]);
3162                    let w = mr[i] + mr[j];
3163                    let a = mag2(v) - w.powi(2);
3164                    let b = inner(u, v) + lij * w;
3165                    let c = mag2(u) - lij.powi(2);
3166                    let d = b.powi(2) - a * c;
3167                    if d < 0.0 {
3168                        continue;
3169                    }
3170
3171                    let sd = d.sqrt();
3172                    for h1 in [(-b + sd) / a, (-b - sd) / a] {
3173                        let lijp = lij - h1 * w;
3174                        if lijp > 0.0 && h1 > 0.0 && h > h1 {
3175                            h = h1;
3176                        }
3177                    }
3178                }
3179            }
3180        }
3181
3182        if h == 1.0e10 {
3183            return Err(TreeError::InvalidOperation(
3184                "polygon inset distance was not found",
3185            ));
3186        }
3187        Ok(h)
3188    }
3189
3190    fn create_spoke_paths(&mut self, poly_id: usize, ring_nodes: &[usize], inset_nodes: &[usize]) {
3191        for (ring_node, inset_node) in ring_nodes.iter().copied().zip(inset_nodes.iter().copied()) {
3192            let path_id = self.create_sub_path(poly_id, ring_node, inset_node, false);
3193            self.polys[poly_id - 1].spoke_paths.push(path_id);
3194        }
3195    }
3196
3197    fn create_sub_node(&mut self, poly_id: usize, loc: Point) -> usize {
3198        let index = self.nodes.len() + 1;
3199        self.nodes.push(Node {
3200            index,
3201            label: String::new(),
3202            loc,
3203            depth: DEPTH_NOT_SET,
3204            elevation: 0.0,
3205            is_leaf: false,
3206            is_sub: true,
3207            is_border: false,
3208            is_pinned: false,
3209            is_polygon: false,
3210            is_junction: false,
3211            is_conditioned: false,
3212            owned_vertices: Vec::new(),
3213            edges: Vec::new(),
3214            leaf_paths: Vec::new(),
3215            owner: OwnerRef::Poly(poly_id),
3216        });
3217        self.polys[poly_id - 1].owned_nodes.push(index);
3218        index
3219    }
3220
3221    fn get_or_make_inset_node(&mut self, poly_id: usize, loc: Point) -> usize {
3222        let owned_nodes = self.polys[poly_id - 1].owned_nodes.clone();
3223        for node_id in owned_nodes {
3224            if self.nodes[node_id - 1].loc.distance(loc) < DIST_TOL {
3225                self.nodes[node_id - 1].is_junction = true;
3226                return node_id;
3227            }
3228        }
3229        self.create_sub_node(poly_id, loc)
3230    }
3231
3232    fn create_sub_path(
3233        &mut self,
3234        poly_id: usize,
3235        front_node: usize,
3236        back_node: usize,
3237        connect_leaf_path: bool,
3238    ) -> usize {
3239        let index = self.paths.len() + 1;
3240        self.paths.push(Path {
3241            index,
3242            min_tree_length: 0.0,
3243            min_paper_length: 0.0,
3244            act_tree_length: 0.0,
3245            act_paper_length: 0.0,
3246            is_leaf: false,
3247            is_sub: true,
3248            is_feasible: false,
3249            is_active: false,
3250            is_border: false,
3251            is_polygon: false,
3252            is_conditioned: false,
3253            fwd_poly: None,
3254            bkd_poly: None,
3255            nodes: vec![front_node, back_node],
3256            edges: Vec::new(),
3257            outset_path: None,
3258            front_reduction: 0.0,
3259            back_reduction: 0.0,
3260            min_depth: DEPTH_NOT_SET,
3261            min_depth_dist: DEPTH_NOT_SET,
3262            owned_vertices: Vec::new(),
3263            owned_creases: Vec::new(),
3264            owner: OwnerRef::Poly(poly_id),
3265        });
3266        self.polys[poly_id - 1].owned_paths.push(index);
3267        if connect_leaf_path {
3268            self.nodes[front_node - 1].leaf_paths.push(index);
3269            self.nodes[back_node - 1].leaf_paths.push(index);
3270        }
3271        index
3272    }
3273
3274    fn owner_paths_for_poly(&self, poly_id: usize) -> Vec<usize> {
3275        match self.polys[poly_id - 1].owner {
3276            OwnerRef::Tree => self.owned_paths.clone(),
3277            OwnerRef::Poly(owner_id) => self
3278                .polys
3279                .get(owner_id.saturating_sub(1))
3280                .map(|poly| poly.owned_paths.clone())
3281                .unwrap_or_default(),
3282            _ => Vec::new(),
3283        }
3284    }
3285
3286    fn find_leaf_path_between_any(&self, node1: usize, node2: usize) -> Option<usize> {
3287        self.nodes[node1 - 1]
3288            .leaf_paths
3289            .iter()
3290            .copied()
3291            .find(|path_id| {
3292                self.paths
3293                    .get(path_id.saturating_sub(1))
3294                    .and_then(|path| path.nodes.first().copied().zip(path.nodes.last().copied()))
3295                    .is_some_and(|(a, b)| a == node2 || b == node2)
3296            })
3297    }
3298
3299    fn build_poly_creases_and_facets(&mut self, poly_id: usize) -> Result<()> {
3300        let nn = self.polys[poly_id - 1].ring_nodes.len();
3301
3302        for i in 0..nn {
3303            let front_node = self.polys[poly_id - 1].ring_nodes[i];
3304            let back_node = self.polys[poly_id - 1].ring_nodes[(i + 1) % nn];
3305            let path_id = self.polys[poly_id - 1].ring_paths[i];
3306
3307            if self.path_is_axial(path_id) || self.path_is_gusset(path_id) {
3308                self.build_self_vertices(path_id)?;
3309            }
3310
3311            if self.path_is_active_axial(path_id) || self.path_is_gusset(path_id) {
3312                let (_, ridge_paths) =
3313                    self.get_ridgeline_nodes_and_paths(poly_id, front_node, back_node)?;
3314                let bottom_vertices = self.paths[path_id - 1].owned_vertices.clone();
3315                let p1 = self.nodes[front_node - 1].loc;
3316                let p2 = self.nodes[back_node - 1].loc;
3317                for bottom_vertex in bottom_vertices {
3318                    let bottom_loc = self.vertices[bottom_vertex - 1].loc;
3319                    let tree_node = self.vertices[bottom_vertex - 1].tree_node;
3320                    for ridge_path in ridge_paths.iter().copied() {
3321                        let q1 = self.nodes[self.paths[ridge_path - 1].nodes[0] - 1].loc;
3322                        let q2 =
3323                            self.nodes[*self.paths[ridge_path - 1].nodes.last().unwrap() - 1].loc;
3324                        if let Some(q) = project_p_to_q(p1, p2, bottom_loc, q1, q2) {
3325                            let top_vertex = self.get_or_make_path_vertex(ridge_path, q, tree_node);
3326                            let outermost_poly = self.outermost_poly(poly_id);
3327                            self.get_or_make_crease(
3328                                OwnerRef::Poly(outermost_poly),
3329                                bottom_vertex,
3330                                top_vertex,
3331                                CREASE_UNFOLDED_HINGE,
3332                            )?;
3333                        }
3334                    }
3335                }
3336            }
3337        }
3338
3339        for i in 0..nn {
3340            let front_node = self.polys[poly_id - 1].ring_nodes[i];
3341            let back_node = self.polys[poly_id - 1].ring_nodes[(i + 1) % nn];
3342            let path_id = self.polys[poly_id - 1].ring_paths[i];
3343
3344            if self.path_is_axial(path_id) || self.path_is_gusset(path_id) {
3345                let ridge_vertices = self.get_ridgeline_vertices(poly_id, front_node, back_node)?;
3346                if let Some((&first, rest)) = ridge_vertices.split_first() {
3347                    let outermost_poly = self.outermost_poly(poly_id);
3348                    let mut ridge_vertex = first;
3349                    for next_ridge_vertex in rest.iter().copied() {
3350                        self.get_or_make_crease(
3351                            OwnerRef::Poly(outermost_poly),
3352                            ridge_vertex,
3353                            next_ridge_vertex,
3354                            CREASE_RIDGE,
3355                        )?;
3356                        ridge_vertex = next_ridge_vertex;
3357                    }
3358                }
3359
3360                if self.path_is_axial(path_id) && !self.paths[path_id - 1].is_active {
3361                    self.propagate_inactive_axial_hinges(poly_id, path_id, &ridge_vertices)?;
3362                }
3363            }
3364
3365            if self.path_is_axial(path_id) || self.path_is_gusset(path_id) {
3366                let crease_kind = if self.path_is_axial(path_id) {
3367                    CREASE_AXIAL
3368                } else {
3369                    CREASE_GUSSET
3370                };
3371                self.connect_self_vertices(path_id, crease_kind)?;
3372            }
3373        }
3374
3375        if self.polys[poly_id - 1].is_sub_poly {
3376            return Ok(());
3377        }
3378
3379        let mut facet_creases = self.polys[poly_id - 1].owned_creases.clone();
3380        for path_id in self.polys[poly_id - 1].ring_paths.clone() {
3381            for crease_id in self.paths[path_id - 1].owned_creases.iter().copied() {
3382                push_unique(&mut facet_creases, crease_id);
3383            }
3384        }
3385        self.build_facets_from_creases(poly_id, &facet_creases)
3386    }
3387
3388    fn path_is_axial(&self, path_id: usize) -> bool {
3389        let path = &self.paths[path_id - 1];
3390        path.is_leaf && path.is_polygon
3391    }
3392
3393    fn path_is_active_axial(&self, path_id: usize) -> bool {
3394        let path = &self.paths[path_id - 1];
3395        path.is_active && path.is_leaf
3396    }
3397
3398    fn path_is_gusset(&self, path_id: usize) -> bool {
3399        let path = &self.paths[path_id - 1];
3400        path.is_active && !path.is_border
3401    }
3402
3403    fn get_or_make_node_vertex(&mut self, node_id: usize) -> usize {
3404        if let Some(vertex_id) = self.nodes[node_id - 1].owned_vertices.first().copied() {
3405            return vertex_id;
3406        }
3407        let index = self.vertices.len() + 1;
3408        let node = &self.nodes[node_id - 1];
3409        self.vertices.push(Vertex {
3410            index,
3411            loc: node.loc,
3412            elevation: node.elevation,
3413            is_border: node.is_border,
3414            tree_node: (!node.is_sub).then_some(node_id),
3415            left_pseudohinge_mate: None,
3416            right_pseudohinge_mate: None,
3417            creases: Vec::new(),
3418            depth: DEPTH_NOT_SET,
3419            discrete_depth: usize::MAX,
3420            cc_flag: 0,
3421            st_flag: 0,
3422            owner: OwnerRef::Node(node_id),
3423        });
3424        self.nodes[node_id - 1].owned_vertices.push(index);
3425        index
3426    }
3427
3428    fn get_or_make_path_vertex(
3429        &mut self,
3430        path_id: usize,
3431        loc: Point,
3432        tree_node: Option<usize>,
3433    ) -> usize {
3434        let front_node = self.paths[path_id - 1].nodes[0];
3435        let back_node = *self.paths[path_id - 1].nodes.last().unwrap();
3436        let vertex_id = if vertices_same_loc(loc, self.nodes[front_node - 1].loc) {
3437            self.get_or_make_node_vertex(front_node)
3438        } else if vertices_same_loc(loc, self.nodes[back_node - 1].loc) {
3439            self.get_or_make_node_vertex(back_node)
3440        } else if let Some(vertex_id) = self.paths[path_id - 1]
3441            .owned_vertices
3442            .iter()
3443            .copied()
3444            .find(|vertex_id| vertices_same_loc(loc, self.vertices[*vertex_id - 1].loc))
3445        {
3446            vertex_id
3447        } else {
3448            self.make_path_vertex(path_id, loc, tree_node)
3449        };
3450
3451        if self.vertices[vertex_id - 1].tree_node.is_none() && tree_node.is_some() {
3452            self.vertices[vertex_id - 1].tree_node = tree_node;
3453        }
3454        vertex_id
3455    }
3456
3457    fn make_path_vertex(&mut self, path_id: usize, loc: Point, tree_node: Option<usize>) -> usize {
3458        let front_node = self.paths[path_id - 1].nodes[0];
3459        let back_node = *self.paths[path_id - 1].nodes.last().unwrap();
3460        let p1 = self.nodes[front_node - 1].loc;
3461        let p2 = self.nodes[back_node - 1].loc;
3462        let dist_p = loc.distance(p1);
3463        let x = dist_p / p2.distance(p1);
3464        let elevation = (1.0 - x) * self.nodes[front_node - 1].elevation
3465            + x * self.nodes[back_node - 1].elevation;
3466
3467        let index = self.vertices.len() + 1;
3468        self.vertices.push(Vertex {
3469            index,
3470            loc,
3471            elevation,
3472            is_border: self.paths[path_id - 1].is_border,
3473            tree_node,
3474            left_pseudohinge_mate: None,
3475            right_pseudohinge_mate: None,
3476            creases: Vec::new(),
3477            depth: DEPTH_NOT_SET,
3478            discrete_depth: usize::MAX,
3479            cc_flag: 0,
3480            st_flag: 0,
3481            owner: OwnerRef::Path(path_id),
3482        });
3483
3484        let insert_at = self.paths[path_id - 1]
3485            .owned_vertices
3486            .iter()
3487            .position(|vertex_id| dist_p < self.vertices[*vertex_id - 1].loc.distance(p1));
3488        if let Some(pos) = insert_at {
3489            self.paths[path_id - 1].owned_vertices.insert(pos, index);
3490        } else {
3491            self.paths[path_id - 1].owned_vertices.push(index);
3492        }
3493
3494        let split = self.paths[path_id - 1]
3495            .owned_creases
3496            .iter()
3497            .copied()
3498            .find_map(|crease_id| {
3499                let crease = &self.creases[crease_id - 1];
3500                let front_vertex = crease.vertices[0];
3501                let back_vertex = crease.vertices[1];
3502                let pc1 = self.vertices[front_vertex - 1].loc;
3503                let pc2 = self.vertices[back_vertex - 1].loc;
3504                let pc21 = point_sub(pc2, pc1);
3505                let x = inner(point_sub(loc, pc1), pc21) / mag2(pc21);
3506                (x > 0.0 && x < 1.0).then_some((crease_id, front_vertex, back_vertex, crease.kind))
3507            });
3508        if let Some((crease_id, front_vertex, back_vertex, kind)) = split {
3509            let _ = self.create_crease(OwnerRef::Path(path_id), front_vertex, index, kind);
3510            let _ = self.create_crease(OwnerRef::Path(path_id), index, back_vertex, kind);
3511            self.delete_creases(&[crease_id]);
3512        }
3513
3514        index
3515    }
3516
3517    fn build_self_vertices(&mut self, path_id: usize) -> Result<()> {
3518        let front_node = self.paths[path_id - 1].nodes[0];
3519        let back_node = *self.paths[path_id - 1].nodes.last().unwrap();
3520        let front_vertex = self.get_or_make_node_vertex(front_node);
3521        let back_vertex = self.get_or_make_node_vertex(back_node);
3522
3523        if !self.paths[path_id - 1].owned_vertices.is_empty() {
3524            return Ok(());
3525        }
3526        if !self.paths[path_id - 1].is_active {
3527            return Ok(());
3528        }
3529
3530        let q1 = self.vertices[front_vertex - 1].loc;
3531        let q2 = self.vertices[back_vertex - 1].loc;
3532        let act_paper_length = self.paths[path_id - 1].act_paper_length;
3533        if act_paper_length == 0.0 {
3534            return Ok(());
3535        }
3536        let qu = point_div(point_sub(q2, q1), act_paper_length);
3537        let (max_outset_path, max_front_reduction, _) = self.max_outset_path(path_id);
3538        let mut cur_pos = -max_front_reduction;
3539        let edge_ids = self.paths[max_outset_path - 1].edges.clone();
3540        let node_ids = self.paths[max_outset_path - 1].nodes.clone();
3541        for (i, edge_id) in edge_ids.iter().copied().enumerate() {
3542            let cur_node = node_ids[i + 1];
3543            cur_pos += self.edges[edge_id - 1].strained_length() * self.scale;
3544            if cur_pos <= 0.0 {
3545                continue;
3546            }
3547            if cur_pos >= act_paper_length {
3548                break;
3549            }
3550            self.get_or_make_path_vertex(
3551                path_id,
3552                point_add(q1, point_mul(qu, cur_pos)),
3553                Some(cur_node),
3554            );
3555        }
3556        Ok(())
3557    }
3558
3559    fn max_outset_path(&self, path_id: usize) -> (usize, TmFloat, TmFloat) {
3560        if let Some(outset_path) = self.paths[path_id - 1].outset_path {
3561            let (max_path, front, back) = self.max_outset_path(outset_path);
3562            (
3563                max_path,
3564                front + self.paths[path_id - 1].front_reduction,
3565                back + self.paths[path_id - 1].back_reduction,
3566            )
3567        } else {
3568            (path_id, 0.0, 0.0)
3569        }
3570    }
3571
3572    fn connect_self_vertices(&mut self, path_id: usize, kind: i32) -> Result<()> {
3573        let front_node = self.paths[path_id - 1].nodes[0];
3574        let back_node = *self.paths[path_id - 1].nodes.last().unwrap();
3575        let mut front_vertex = self
3576            .nodes
3577            .get(front_node - 1)
3578            .and_then(|node| node.owned_vertices.first().copied())
3579            .ok_or(TreeError::InvalidOperation("path front vertex missing"))?;
3580        for back_vertex in self.paths[path_id - 1].owned_vertices.clone() {
3581            self.get_or_make_crease(OwnerRef::Path(path_id), front_vertex, back_vertex, kind)?;
3582            front_vertex = back_vertex;
3583        }
3584        let back_vertex = self
3585            .nodes
3586            .get(back_node - 1)
3587            .and_then(|node| node.owned_vertices.first().copied())
3588            .ok_or(TreeError::InvalidOperation("path back vertex missing"))?;
3589        self.get_or_make_crease(OwnerRef::Path(path_id), front_vertex, back_vertex, kind)?;
3590        Ok(())
3591    }
3592
3593    fn get_ridgeline_nodes_and_paths(
3594        &self,
3595        poly_id: usize,
3596        front_node: usize,
3597        back_node: usize,
3598    ) -> Result<(Vec<usize>, Vec<usize>)> {
3599        let poly = &self.polys[poly_id - 1];
3600        let front_offset = poly
3601            .ring_nodes
3602            .iter()
3603            .position(|node| *node == front_node)
3604            .ok_or(TreeError::InvalidOperation(
3605                "ridgeline front node not in poly",
3606            ))?;
3607        let back_offset = poly
3608            .ring_nodes
3609            .iter()
3610            .position(|node| *node == back_node)
3611            .ok_or(TreeError::InvalidOperation(
3612                "ridgeline back node not in poly",
3613            ))?;
3614
3615        let mut ridge_nodes = vec![front_node];
3616        let mut ridge_paths = vec![poly.spoke_paths[front_offset]];
3617        match poly.owned_nodes.len() {
3618            1 => ridge_nodes.push(poly.owned_nodes[0]),
3619            2 => {
3620                let front_inset = poly.inset_nodes[front_offset];
3621                let back_inset = poly.inset_nodes[back_offset];
3622                ridge_nodes.push(front_inset);
3623                if front_inset != back_inset {
3624                    ridge_paths.push(
3625                        poly.ridge_path
3626                            .ok_or(TreeError::InvalidOperation("ridgeline path missing"))?,
3627                    );
3628                    ridge_nodes.push(back_inset);
3629                }
3630            }
3631            _ => {
3632                let front_inset = poly.inset_nodes[front_offset];
3633                let back_inset = poly.inset_nodes[back_offset];
3634                if front_inset == back_inset {
3635                    ridge_nodes.push(front_inset);
3636                } else {
3637                    let sub_poly = poly
3638                        .owned_polys
3639                        .iter()
3640                        .copied()
3641                        .find(|sub_poly_id| {
3642                            let sub_poly = &self.polys[*sub_poly_id - 1];
3643                            sub_poly.ring_nodes.contains(&front_inset)
3644                                && sub_poly.ring_nodes.contains(&back_inset)
3645                        })
3646                        .ok_or(TreeError::InvalidOperation("ridgeline subpoly missing"))?;
3647                    let (sub_nodes, sub_paths) =
3648                        self.get_ridgeline_nodes_and_paths(sub_poly, front_inset, back_inset)?;
3649                    ridge_nodes.extend(sub_nodes);
3650                    ridge_paths.extend(sub_paths);
3651                }
3652            }
3653        }
3654        ridge_paths.push(poly.spoke_paths[back_offset]);
3655        ridge_nodes.push(back_node);
3656        Ok((ridge_nodes, ridge_paths))
3657    }
3658
3659    fn get_ridgeline_vertices(
3660        &mut self,
3661        poly_id: usize,
3662        front_node: usize,
3663        back_node: usize,
3664    ) -> Result<Vec<usize>> {
3665        let (ridge_nodes, ridge_paths) =
3666            self.get_ridgeline_nodes_and_paths(poly_id, front_node, back_node)?;
3667        let p1 = self.nodes[front_node - 1].loc;
3668        let p2 = self.nodes[back_node - 1].loc;
3669        let mut vertices = Vec::new();
3670        for ridge_node in ridge_nodes {
3671            if self.nodes[ridge_node - 1].is_junction {
3672                self.get_or_make_node_vertex(ridge_node);
3673            }
3674            if let Some(vertex_id) = self.nodes[ridge_node - 1].owned_vertices.first().copied() {
3675                vertices.push(vertex_id);
3676            }
3677        }
3678        for ridge_path in ridge_paths {
3679            vertices.extend(self.paths[ridge_path - 1].owned_vertices.iter().copied());
3680        }
3681        vertices.sort_by(|a, b| {
3682            sortable_ridge_vertex_value(self.vertices[*a - 1].loc, p1, p2).total_cmp(
3683                &sortable_ridge_vertex_value(self.vertices[*b - 1].loc, p1, p2),
3684            )
3685        });
3686        Ok(vertices)
3687    }
3688
3689    fn propagate_inactive_axial_hinges(
3690        &mut self,
3691        poly_id: usize,
3692        path_id: usize,
3693        ridge_vertices: &[usize],
3694    ) -> Result<()> {
3695        if ridge_vertices.len() < 3 {
3696            return Ok(());
3697        }
3698        let front_node = self.paths[path_id - 1].nodes[0];
3699        let back_node = *self.paths[path_id - 1].nodes.last().unwrap();
3700        let front_vertex = self
3701            .nodes
3702            .get(front_node - 1)
3703            .and_then(|node| node.owned_vertices.first().copied())
3704            .ok_or(TreeError::InvalidOperation(
3705                "inactive axial front vertex missing",
3706            ))?;
3707        let back_vertex = self
3708            .nodes
3709            .get(back_node - 1)
3710            .and_then(|node| node.owned_vertices.first().copied())
3711            .ok_or(TreeError::InvalidOperation(
3712                "inactive axial back vertex missing",
3713            ))?;
3714        let p1 = self.vertices[front_vertex - 1].loc;
3715        let p2 = self.vertices[back_vertex - 1].loc;
3716        let mut crease0 = None;
3717        let mut crease1 = None;
3718        let mut crease2 = None;
3719
3720        for m in 1..ridge_vertices.len() - 1 {
3721            let ridge_vertex = ridge_vertices[m];
3722            let tree_node = self.vertices[ridge_vertex - 1].tree_node;
3723            let mut needs_crease = tree_node.is_some();
3724            let mut kind = CREASE_UNFOLDED_HINGE;
3725            if !needs_crease {
3726                let prev_tree_node = self.vertices[ridge_vertices[m - 1] - 1].tree_node;
3727                let next_tree_node = self.vertices[ridge_vertices[m + 1] - 1].tree_node;
3728                if prev_tree_node.is_some() && prev_tree_node == next_tree_node {
3729                    needs_crease = true;
3730                    kind = CREASE_PSEUDOHINGE;
3731                }
3732            }
3733            if !needs_crease {
3734                continue;
3735            }
3736            if let Some(p) = project_q_to_p(self.vertices[ridge_vertex - 1].loc, p1, p2) {
3737                let bottom_vertex = self.get_or_make_path_vertex(path_id, p, tree_node);
3738                crease2 = crease1;
3739                crease1 = crease0;
3740                crease0 = Some(self.get_or_make_crease(
3741                    OwnerRef::Poly(self.outermost_poly(poly_id)),
3742                    bottom_vertex,
3743                    ridge_vertex,
3744                    kind,
3745                )?);
3746            }
3747            if let (Some(c0), Some(c1), Some(c2)) = (crease0, crease1, crease2)
3748                && self.creases[c0 - 1].kind == CREASE_UNFOLDED_HINGE
3749                && self.creases[c1 - 1].kind == CREASE_PSEUDOHINGE
3750                && self.creases[c2 - 1].kind == CREASE_UNFOLDED_HINGE
3751            {
3752                let mate0 = self.lower_crease_vertex(c0);
3753                let mate2 = self.lower_crease_vertex(c2);
3754                self.vertices[mate0 - 1].right_pseudohinge_mate = Some(mate2);
3755                self.vertices[mate2 - 1].left_pseudohinge_mate = Some(mate0);
3756            }
3757        }
3758        Ok(())
3759    }
3760
3761    fn outermost_poly(&self, mut poly_id: usize) -> usize {
3762        while let OwnerRef::Poly(owner_id) = self.polys[poly_id - 1].owner {
3763            poly_id = owner_id;
3764        }
3765        poly_id
3766    }
3767
3768    fn lower_crease_vertex(&self, crease_id: usize) -> usize {
3769        let crease = &self.creases[crease_id - 1];
3770        let v1 = crease.vertices[0];
3771        let v2 = crease.vertices[1];
3772        if self.vertices[v1 - 1].elevation < self.vertices[v2 - 1].elevation {
3773            v1
3774        } else {
3775            v2
3776        }
3777    }
3778
3779    fn get_or_make_crease(
3780        &mut self,
3781        owner: OwnerRef,
3782        vertex1: usize,
3783        vertex2: usize,
3784        kind: i32,
3785    ) -> Result<usize> {
3786        let owned_creases = self.owned_creases_for_owner(&owner);
3787        if let Some(crease_id) = owned_creases.into_iter().find(|crease_id| {
3788            let crease = &self.creases[*crease_id - 1];
3789            matches!(
3790                crease.vertices.first().copied().zip(crease.vertices.last().copied()),
3791                Some((a, b)) if (a == vertex1 && b == vertex2) || (a == vertex2 && b == vertex1)
3792            )
3793        }) {
3794            return Ok(crease_id);
3795        }
3796        self.create_crease(owner, vertex1, vertex2, kind)
3797    }
3798
3799    fn create_crease(
3800        &mut self,
3801        owner: OwnerRef,
3802        vertex1: usize,
3803        vertex2: usize,
3804        kind: i32,
3805    ) -> Result<usize> {
3806        if vertex1 == vertex2 {
3807            return Err(TreeError::InvalidOperation(
3808                "crease endpoints must be distinct vertices",
3809            ));
3810        }
3811        let index = self.creases.len() + 1;
3812        self.creases.push(Crease {
3813            index,
3814            kind,
3815            vertices: vec![vertex1, vertex2],
3816            fwd_facet: None,
3817            bkd_facet: None,
3818            fold: FOLD_FLAT,
3819            cc_flag: 0,
3820            st_flag: 0,
3821            owner: owner.clone(),
3822        });
3823        match owner {
3824            OwnerRef::Path(path_id) => self.paths[path_id - 1].owned_creases.push(index),
3825            OwnerRef::Poly(poly_id) => self.polys[poly_id - 1].owned_creases.push(index),
3826            _ => {}
3827        }
3828        self.vertices[vertex1 - 1].creases.push(index);
3829        self.vertices[vertex2 - 1].creases.push(index);
3830        Ok(index)
3831    }
3832
3833    fn owned_creases_for_owner(&self, owner: &OwnerRef) -> Vec<usize> {
3834        match *owner {
3835            OwnerRef::Path(path_id) => self
3836                .paths
3837                .get(path_id.saturating_sub(1))
3838                .map(|path| path.owned_creases.clone())
3839                .unwrap_or_default(),
3840            OwnerRef::Poly(poly_id) => self
3841                .polys
3842                .get(poly_id.saturating_sub(1))
3843                .map(|poly| poly.owned_creases.clone())
3844                .unwrap_or_default(),
3845            _ => Vec::new(),
3846        }
3847    }
3848
3849    fn build_facets_from_creases(&mut self, poly_id: usize, crease_list: &[usize]) -> Result<()> {
3850        if crease_list.is_empty() {
3851            return Ok(());
3852        }
3853        for crease_id in crease_list.iter().copied() {
3854            if self.can_start_facet_fwd(poly_id, crease_id) {
3855                self.build_facet_ring(poly_id, crease_id, true)?;
3856            }
3857            if self.can_start_facet_bkd(poly_id, crease_id) {
3858                self.build_facet_ring(poly_id, crease_id, false)?;
3859            }
3860        }
3861        Ok(())
3862    }
3863
3864    fn can_start_facet_fwd(&self, poly_id: usize, crease_id: usize) -> bool {
3865        let crease = &self.creases[crease_id - 1];
3866        if crease.fwd_facet.is_some() {
3867            return false;
3868        }
3869        if crease.kind != CREASE_AXIAL {
3870            return true;
3871        }
3872        are_ccw(
3873            self.vertices[crease.vertices[0] - 1].loc,
3874            self.vertices[crease.vertices[1] - 1].loc,
3875            self.polys[poly_id - 1].centroid,
3876        )
3877    }
3878
3879    fn can_start_facet_bkd(&self, poly_id: usize, crease_id: usize) -> bool {
3880        let crease = &self.creases[crease_id - 1];
3881        if crease.bkd_facet.is_some() {
3882            return false;
3883        }
3884        if crease.kind != CREASE_AXIAL {
3885            return true;
3886        }
3887        are_cw(
3888            self.vertices[crease.vertices[0] - 1].loc,
3889            self.vertices[crease.vertices[1] - 1].loc,
3890            self.polys[poly_id - 1].centroid,
3891        )
3892    }
3893
3894    fn build_facet_ring(&mut self, poly_id: usize, crease_id: usize, fwd: bool) -> Result<()> {
3895        let facet_id = self.create_facet(poly_id);
3896        if fwd {
3897            self.creases[crease_id - 1].fwd_facet = Some(facet_id);
3898        } else {
3899            self.creases[crease_id - 1].bkd_facet = Some(facet_id);
3900        }
3901
3902        let first_vertex = if fwd {
3903            self.creases[crease_id - 1].vertices[0]
3904        } else {
3905            self.creases[crease_id - 1].vertices[1]
3906        };
3907        let mut this_vertex = if fwd {
3908            self.creases[crease_id - 1].vertices[1]
3909        } else {
3910            self.creases[crease_id - 1].vertices[0]
3911        };
3912        let mut this_crease = crease_id;
3913        let mut vertices = vec![first_vertex];
3914        let mut creases = vec![this_crease];
3915        let mut too_many = 0;
3916
3917        loop {
3918            let (next_crease, next_vertex) =
3919                self.next_crease_and_vertex(this_crease, this_vertex)?;
3920            vertices.push(this_vertex);
3921            creases.push(next_crease);
3922            if self.creases[next_crease - 1].vertices.first().copied() == Some(this_vertex) {
3923                self.creases[next_crease - 1].fwd_facet = Some(facet_id);
3924            } else {
3925                self.creases[next_crease - 1].bkd_facet = Some(facet_id);
3926            }
3927            this_crease = next_crease;
3928            this_vertex = next_vertex;
3929            too_many += 1;
3930            if next_vertex == first_vertex {
3931                break;
3932            }
3933            if too_many >= 100 {
3934                return Err(TreeError::InvalidOperation(
3935                    "facet ring walk exceeded TreeMaker guard",
3936                ));
3937            }
3938        }
3939
3940        self.facets[facet_id - 1].vertices = vertices;
3941        self.facets[facet_id - 1].creases = creases;
3942        self.calc_facet_contents(facet_id);
3943        Ok(())
3944    }
3945
3946    fn create_facet(&mut self, poly_id: usize) -> usize {
3947        let index = self.facets.len() + 1;
3948        self.facets.push(Facet {
3949            index,
3950            centroid: Point { x: 0.0, y: 0.0 },
3951            is_well_formed: true,
3952            vertices: Vec::new(),
3953            creases: Vec::new(),
3954            corridor_edge: None,
3955            head_facets: Vec::new(),
3956            tail_facets: Vec::new(),
3957            order: usize::MAX,
3958            color: 0,
3959            owner: OwnerRef::Poly(poly_id),
3960        });
3961        self.polys[poly_id - 1].owned_facets.push(index);
3962        index
3963    }
3964
3965    fn next_crease_and_vertex(
3966        &self,
3967        this_crease: usize,
3968        this_vertex: usize,
3969    ) -> Result<(usize, usize)> {
3970        let crease = &self.creases[this_crease - 1];
3971        let mut that_vertex = crease.vertices[0];
3972        if that_vertex == this_vertex {
3973            that_vertex = crease.vertices[1];
3974        }
3975        let this_angle = angle(point_sub(
3976            self.vertices[that_vertex - 1].loc,
3977            self.vertices[this_vertex - 1].loc,
3978        ));
3979
3980        let mut delta = TWO_PI;
3981        let mut next_crease = None;
3982        let mut next_vertex = None;
3983        for candidate_crease in self.vertices[this_vertex - 1].creases.iter().copied() {
3984            if candidate_crease == this_crease {
3985                continue;
3986            }
3987            let candidate = &self.creases[candidate_crease - 1];
3988            let mut candidate_vertex = candidate.vertices[0];
3989            if candidate_vertex == this_vertex {
3990                candidate_vertex = candidate.vertices[1];
3991            }
3992            let next_angle = angle(point_sub(
3993                self.vertices[candidate_vertex - 1].loc,
3994                self.vertices[this_vertex - 1].loc,
3995            ));
3996            let mut new_delta = this_angle - next_angle;
3997            while new_delta < 0.0 {
3998                new_delta += TWO_PI;
3999            }
4000            while new_delta >= TWO_PI {
4001                new_delta -= TWO_PI;
4002            }
4003            if new_delta < delta {
4004                delta = new_delta;
4005                next_crease = Some(candidate_crease);
4006                next_vertex = Some(candidate_vertex);
4007            }
4008        }
4009
4010        match (next_crease, next_vertex) {
4011            (Some(crease), Some(vertex)) => Ok((crease, vertex)),
4012            _ => Err(TreeError::InvalidOperation(
4013                "facet crease walk could not advance",
4014            )),
4015        }
4016    }
4017
4018    fn calc_facet_contents(&mut self, facet_id: usize) {
4019        let mut centroid = Point { x: 0.0, y: 0.0 };
4020        let vertices = self.facets[facet_id - 1].vertices.clone();
4021        for vertex_id in vertices.iter().copied() {
4022            centroid = point_add(centroid, self.vertices[vertex_id - 1].loc);
4023        }
4024        if !vertices.is_empty() {
4025            centroid = point_div(centroid, vertices.len() as TmFloat);
4026        }
4027        self.facets[facet_id - 1].centroid = centroid;
4028        self.facets[facet_id - 1].is_well_formed = true;
4029
4030        let num_vertices = self.facets[facet_id - 1].vertices.len();
4031        let mut rotations = 0;
4032        while self.facets[facet_id - 1]
4033            .creases
4034            .first()
4035            .is_some_and(|crease_id| !self.crease_is_axial_or_gusset(*crease_id))
4036        {
4037            self.facets[facet_id - 1].vertices.rotate_left(1);
4038            self.facets[facet_id - 1].creases.rotate_left(1);
4039            rotations += 1;
4040            if rotations >= num_vertices {
4041                self.facets[facet_id - 1].is_well_formed = false;
4042                break;
4043            }
4044        }
4045    }
4046
4047    fn crease_is_axial_or_gusset(&self, crease_id: usize) -> bool {
4048        matches!(
4049            self.creases[crease_id - 1].kind,
4050            CREASE_AXIAL | CREASE_GUSSET
4051        )
4052    }
4053
4054    fn find_any_path_in(&self, path_ids: &[usize], node1: usize, node2: usize) -> Option<usize> {
4055        path_ids.iter().copied().find(|path_id| {
4056            self.paths
4057                .get(path_id.saturating_sub(1))
4058                .and_then(|path| path.nodes.first().copied().zip(path.nodes.last().copied()))
4059                .is_some_and(|(a, b)| (a == node1 && b == node2) || (a == node2 && b == node1))
4060        })
4061    }
4062
4063    fn paths_intersect_interior(&self, path1: usize, path2: usize) -> bool {
4064        let path1 = &self.paths[path1 - 1];
4065        let path2 = &self.paths[path2 - 1];
4066        let Some((path1_front, path1_back)) = path1
4067            .nodes
4068            .first()
4069            .copied()
4070            .zip(path1.nodes.last().copied())
4071        else {
4072            return false;
4073        };
4074        let Some((path2_front, path2_back)) = path2
4075            .nodes
4076            .first()
4077            .copied()
4078            .zip(path2.nodes.last().copied())
4079        else {
4080            return false;
4081        };
4082        if path1_front == path2_front
4083            || path1_front == path2_back
4084            || path1_back == path2_front
4085            || path1_back == path2_back
4086        {
4087            return false;
4088        }
4089
4090        let p = self.nodes[path1_front - 1].loc;
4091        let rp = point_sub(self.nodes[path1_back - 1].loc, p);
4092        let q = self.nodes[path2_front - 1].loc;
4093        let rq = point_sub(self.nodes[path2_back - 1].loc, q);
4094        let Some((tp, tq)) = line_intersection_params(p, rp, q, rq) else {
4095            return false;
4096        };
4097        if tp <= 0.0 || tp >= 1.0 || tq <= 0.0 || tq >= 1.0 {
4098            return false;
4099        }
4100        true
4101    }
4102
4103    fn node_centroid(&self, node_ids: &[usize]) -> Point {
4104        let mut centroid = Point { x: 0.0, y: 0.0 };
4105        for node_id in node_ids {
4106            let loc = self.nodes[*node_id - 1].loc;
4107            centroid.x += loc.x;
4108            centroid.y += loc.y;
4109        }
4110        centroid.x /= node_ids.len() as TmFloat;
4111        centroid.y /= node_ids.len() as TmFloat;
4112        centroid
4113    }
4114
4115    fn cleanup_after_edit(&mut self) {
4116        self.is_feasible = false;
4117        self.is_polygon_valid = false;
4118        self.is_polygon_filled = false;
4119        self.is_vertex_depth_valid = false;
4120        self.is_facet_data_valid = false;
4121        self.is_local_root_connectable = false;
4122
4123        self.kill_invalid_conditions();
4124
4125        if self.owned_nodes.is_empty() {
4126            self.needs_cleanup = false;
4127            return;
4128        }
4129
4130        for node_id in self.owned_nodes.iter().copied() {
4131            let node = &mut self.nodes[node_id - 1];
4132            node.loc.x = node.loc.x.clamp(0.0, self.paper_width);
4133            node.loc.y = node.loc.y.clamp(0.0, self.paper_height);
4134            node.is_border = false;
4135            node.is_pinned = false;
4136            node.is_polygon = false;
4137            node.is_conditioned = false;
4138        }
4139
4140        for edge_id in self.owned_edges.iter().copied() {
4141            let edge = &mut self.edges[edge_id - 1];
4142            edge.is_pinned = false;
4143            edge.is_conditioned = false;
4144        }
4145
4146        let node_locs: Vec<Point> = self.nodes.iter().map(|n| n.loc).collect();
4147        let edge_lengths: Vec<TmFloat> = self.edges.iter().map(Edge::strained_length).collect();
4148        for path_id in self.owned_paths.iter().copied() {
4149            let path = &mut self.paths[path_id - 1];
4150            path.min_tree_length = path
4151                .edges
4152                .iter()
4153                .filter_map(|id| edge_lengths.get(id.saturating_sub(1)))
4154                .sum();
4155            path.min_paper_length = path.min_tree_length * self.scale;
4156            if path.is_leaf && path.nodes.len() >= 2 {
4157                let a = node_locs[path.nodes[0] - 1];
4158                let b = node_locs[*path.nodes.last().unwrap() - 1];
4159                path.act_paper_length = a.distance(b);
4160                path.act_tree_length = path.act_paper_length / self.scale;
4161                path.is_feasible = path.act_paper_length >= path.min_paper_length - DIST_TOL;
4162                path.is_active = (path.act_paper_length - path.min_paper_length).abs() < DIST_TOL;
4163            } else {
4164                path.act_paper_length = 0.0;
4165                path.act_tree_length = 0.0;
4166                path.is_feasible = false;
4167                path.is_active = false;
4168            }
4169            path.is_border = false;
4170            path.is_polygon = false;
4171            path.is_conditioned = false;
4172        }
4173
4174        let leaf_nodes = self.leaf_nodes_in_owned_order();
4175        let leaf_paths = self.leaf_paths_in_owned_order();
4176        let leaf_paths_feasible = leaf_paths
4177            .iter()
4178            .copied()
4179            .all(|path_id| self.paths[path_id - 1].is_feasible);
4180        let condition_feasibilities: Vec<bool> = self
4181            .conditions
4182            .iter()
4183            .map(|condition| condition.kind.calc_feasibility(self))
4184            .collect();
4185        for (condition, feasible) in self
4186            .conditions
4187            .iter_mut()
4188            .zip(condition_feasibilities.iter().copied())
4189        {
4190            condition.is_feasible = feasible;
4191        }
4192        let conditions_feasible = condition_feasibilities.into_iter().all(|feasible| feasible);
4193        self.is_feasible = leaf_paths_feasible && conditions_feasible;
4194        self.rebuild_conditioned_flags();
4195        self.calc_border_nodes_and_paths(&leaf_nodes);
4196        self.calc_pinned_nodes_and_edges(&leaf_nodes, &leaf_paths);
4197        self.calc_polygon_network(&leaf_nodes, &leaf_paths);
4198        self.calc_polygon_validity(&leaf_nodes);
4199        self.kill_orphan_vertices_and_creases();
4200        self.promote_first_tree_node_to_root();
4201        self.renumber_part_indices();
4202        self.clear_crease_pattern_cleanup_data();
4203        self.calc_polygon_filled();
4204        if !self.is_polygon_filled {
4205            self.needs_cleanup = false;
4206            return;
4207        }
4208        self.calc_depth_and_bend();
4209        self.calc_vertex_depth_validity();
4210        if !self.is_vertex_depth_valid {
4211            self.needs_cleanup = false;
4212            return;
4213        }
4214        self.calc_facet_data_validity();
4215        if !self.is_facet_data_valid {
4216            self.needs_cleanup = false;
4217            return;
4218        }
4219        self.calc_facet_corridor_edges();
4220        self.calc_facet_order();
4221        if !self.is_local_root_connectable {
4222            self.needs_cleanup = false;
4223            return;
4224        }
4225        self.calc_facet_color();
4226        self.calc_fold_directions();
4227        self.needs_cleanup = false;
4228    }
4229
4230    fn kill_invalid_conditions(&mut self) {
4231        let mut conditions = std::mem::take(&mut self.conditions);
4232        conditions.retain(|condition| self.condition_is_valid(&condition.kind));
4233        for (i, condition) in conditions.iter_mut().enumerate() {
4234            condition.index = i + 1;
4235        }
4236        self.conditions = conditions;
4237    }
4238
4239    fn condition_is_valid(&self, kind: &ConditionKind) -> bool {
4240        let node_is_leaf = |node: usize| {
4241            node.checked_sub(1)
4242                .and_then(|index| self.nodes.get(index))
4243                .is_some_and(|node| node.is_leaf)
4244        };
4245        let node_exists = |node: usize| node > 0 && node <= self.nodes.len();
4246        let edge_exists = |edge: usize| edge > 0 && edge <= self.edges.len();
4247        let path_exists_between =
4248            |node1: usize, node2: usize| self.find_leaf_path_between(node1, node2).is_some();
4249
4250        match *kind {
4251            ConditionKind::NodeCombo { node, .. }
4252            | ConditionKind::NodeFixed { node, .. }
4253            | ConditionKind::NodeOnCorner { node }
4254            | ConditionKind::NodeOnEdge { node }
4255            | ConditionKind::NodeSymmetric { node } => node_is_leaf(node),
4256            ConditionKind::NodesPaired { node1, node2 } => {
4257                node_is_leaf(node1) && node_is_leaf(node2)
4258            }
4259            ConditionKind::NodesCollinear {
4260                node1,
4261                node2,
4262                node3,
4263            } => node_is_leaf(node1) && node_is_leaf(node2) && node_is_leaf(node3),
4264            ConditionKind::EdgeLengthFixed { edge } => edge_exists(edge),
4265            ConditionKind::EdgesSameStrain { edge1, edge2 } => {
4266                edge_exists(edge1) && edge_exists(edge2)
4267            }
4268            ConditionKind::PathActive { node1, node2 }
4269            | ConditionKind::PathAngleFixed { node1, node2, .. }
4270            | ConditionKind::PathAngleQuant { node1, node2, .. } => {
4271                node_exists(node1) && node_exists(node2) && path_exists_between(node1, node2)
4272            }
4273            ConditionKind::PathCombo { node1, node2, .. } => {
4274                node_exists(node1) && node_exists(node2) && path_exists_between(node1, node2)
4275            }
4276        }
4277    }
4278
4279    fn leaf_paths_in_owned_order(&self) -> Vec<usize> {
4280        self.owned_paths
4281            .iter()
4282            .copied()
4283            .filter(|id| self.paths[id - 1].is_leaf)
4284            .collect()
4285    }
4286
4287    fn calc_border_nodes_and_paths(&mut self, leaf_nodes: &[usize]) {
4288        if leaf_nodes.len() < 3 {
4289            return;
4290        }
4291
4292        let start_pt = Point { x: -1.0, y: -1.0 };
4293        let Some((&start_node, _)) = leaf_nodes
4294            .iter()
4295            .map(|id| (id, angle(point_sub(self.nodes[*id - 1].loc, start_pt))))
4296            .min_by(|(_, a), (_, b)| a.total_cmp(b))
4297        else {
4298            return;
4299        };
4300
4301        let mut border_nodes = vec![start_node];
4302        let mut prev_node = start_node;
4303        let mut prev_pt = start_pt;
4304        let mut this_node = start_node;
4305        let mut this_pt = self.nodes[this_node - 1].loc;
4306
4307        loop {
4308            let mut best_node = None;
4309            let mut best_angle = TWO_PI;
4310            let mut best_dist = TmFloat::INFINITY;
4311
4312            for node_id in leaf_nodes.iter().copied() {
4313                if node_id == prev_node || node_id == this_node {
4314                    continue;
4315                }
4316                let the_pt = self.nodes[node_id - 1].loc;
4317                let the_angle = angle_change(prev_pt, this_pt, the_pt);
4318                if the_angle < -PI / 2.0 {
4319                    continue;
4320                }
4321                let the_dist = this_pt.distance(the_pt);
4322                if the_angle < best_angle - CONVEXITY_TOL
4323                    || ((the_angle - best_angle).abs() < CONVEXITY_TOL && the_dist < best_dist)
4324                {
4325                    best_node = Some(node_id);
4326                    best_angle = the_angle;
4327                    best_dist = the_dist;
4328                }
4329            }
4330
4331            let Some(next_node) = best_node else {
4332                return;
4333            };
4334            if next_node == start_node {
4335                break;
4336            }
4337            border_nodes.push(next_node);
4338            prev_node = this_node;
4339            prev_pt = this_pt;
4340            this_node = next_node;
4341            this_pt = self.nodes[this_node - 1].loc;
4342        }
4343
4344        if let Some(first) = border_nodes.first().copied() {
4345            self.nodes[first - 1].is_border = true;
4346        }
4347        for i in 1..border_nodes.len() {
4348            let prev = border_nodes[i - 1];
4349            let next = border_nodes[i];
4350            self.nodes[next - 1].is_border = true;
4351            if let Some(path_id) = self.leaf_path_id_between(prev, next) {
4352                self.paths[path_id - 1].is_border = true;
4353            }
4354        }
4355        if let Some(path_id) = self.leaf_path_id_between(
4356            *border_nodes.last().unwrap(),
4357            *border_nodes.first().unwrap(),
4358        ) {
4359            self.paths[path_id - 1].is_border = true;
4360        }
4361    }
4362
4363    fn calc_pinned_nodes_and_edges(&mut self, leaf_nodes: &[usize], leaf_paths: &[usize]) {
4364        for node_id in leaf_nodes.iter().copied() {
4365            self.nodes[node_id - 1].is_pinned = self.calc_is_pinned_node(node_id);
4366        }
4367
4368        for path_id in leaf_paths.iter().copied() {
4369            let path = &self.paths[path_id - 1];
4370            if !path.is_active || path.nodes.len() < 2 {
4371                continue;
4372            }
4373            let node1 = path.nodes[0];
4374            let node2 = *path.nodes.last().unwrap();
4375            if self.nodes[node1 - 1].is_pinned && self.nodes[node2 - 1].is_pinned {
4376                let edge_ids = path.edges.clone();
4377                for edge_id in edge_ids {
4378                    self.edges[edge_id - 1].is_pinned = true;
4379                }
4380            }
4381        }
4382    }
4383
4384    fn calc_is_pinned_node(&self, node_id: usize) -> bool {
4385        let node = &self.nodes[node_id - 1];
4386        let mut angles = Vec::new();
4387        for path_id in &node.leaf_paths {
4388            let path = &self.paths[*path_id - 1];
4389            if !path.is_active || path.nodes.len() < 2 {
4390                continue;
4391            }
4392            let other = if path.nodes[0] == node_id {
4393                *path.nodes.last().unwrap()
4394            } else {
4395                path.nodes[0]
4396            };
4397            angles.push(angle(point_sub(self.nodes[other - 1].loc, node.loc)));
4398        }
4399
4400        if is_tiny(node.loc.x) {
4401            angles.push(-PI);
4402        }
4403        if is_tiny(node.loc.x - self.paper_width) {
4404            angles.push(0.0);
4405        }
4406        if is_tiny(node.loc.y - self.paper_height) {
4407            angles.push(PI / 2.0);
4408        }
4409        if is_tiny(node.loc.y) {
4410            angles.push(-PI / 2.0);
4411        }
4412
4413        if angles.len() < 2 {
4414            return false;
4415        }
4416        angles.sort_by(TmFloat::total_cmp);
4417        for i in 0..angles.len() - 1 {
4418            if angles[i + 1] - angles[i] > PI + CONVEXITY_TOL {
4419                return false;
4420            }
4421        }
4422        angles[0] - angles[angles.len() - 1] + PI <= CONVEXITY_TOL
4423    }
4424
4425    fn calc_polygon_network(&mut self, leaf_nodes: &[usize], leaf_paths: &[usize]) {
4426        for path_id in leaf_paths.iter().copied() {
4427            let path = &mut self.paths[path_id - 1];
4428            path.is_polygon = path.is_active || (path.is_border && path.is_feasible);
4429        }
4430
4431        for node_id in leaf_nodes.iter().copied() {
4432            let node = &mut self.nodes[node_id - 1];
4433            if node.is_pinned || node.is_border {
4434                node.is_polygon = true;
4435            }
4436        }
4437
4438        for path_id in leaf_paths.iter().copied() {
4439            let path = &self.paths[path_id - 1];
4440            if path.is_feasible || path.nodes.len() < 2 {
4441                continue;
4442            }
4443            let node1 = path.nodes[0];
4444            let node2 = *path.nodes.last().unwrap();
4445            self.nodes[node1 - 1].is_polygon = false;
4446            self.nodes[node1 - 1].is_pinned = false;
4447            self.nodes[node2 - 1].is_polygon = false;
4448            self.nodes[node2 - 1].is_pinned = false;
4449        }
4450
4451        loop {
4452            let mut something_changed = false;
4453
4454            for path_id in leaf_paths.iter().copied() {
4455                let path = &self.paths[path_id - 1];
4456                if !path.is_polygon || path.nodes.len() < 2 {
4457                    continue;
4458                }
4459                let node1 = path.nodes[0];
4460                let node2 = *path.nodes.last().unwrap();
4461                if !(self.nodes[node1 - 1].is_polygon && self.nodes[node2 - 1].is_polygon) {
4462                    self.paths[path_id - 1].is_polygon = false;
4463                    something_changed = true;
4464                }
4465            }
4466
4467            for node_id in leaf_nodes.iter().copied() {
4468                if !self.nodes[node_id - 1].is_polygon {
4469                    continue;
4470                }
4471                let poly_paths = self.nodes[node_id - 1]
4472                    .leaf_paths
4473                    .iter()
4474                    .filter(|path_id| self.paths[**path_id - 1].is_polygon)
4475                    .count();
4476                if poly_paths < 2 {
4477                    self.nodes[node_id - 1].is_polygon = false;
4478                    something_changed = true;
4479                }
4480            }
4481
4482            if !something_changed {
4483                break;
4484            }
4485        }
4486
4487        let doomed_polys: Vec<usize> = self
4488            .polys
4489            .iter()
4490            .filter(|poly| !self.calc_poly_is_valid(poly.index, leaf_nodes))
4491            .map(|poly| poly.index)
4492            .collect();
4493        if !doomed_polys.is_empty() {
4494            self.delete_polys(&doomed_polys);
4495        }
4496    }
4497
4498    fn calc_polygon_validity(&mut self, leaf_nodes: &[usize]) {
4499        self.is_polygon_valid = true;
4500        for node_id in leaf_nodes.iter().copied() {
4501            let polygon_paths = self.nodes[node_id - 1]
4502                .leaf_paths
4503                .iter()
4504                .filter(|path_id| self.paths[**path_id - 1].is_polygon)
4505                .count();
4506            if polygon_paths < 2 {
4507                self.is_polygon_valid = false;
4508                return;
4509            }
4510        }
4511
4512        for path_id in self.owned_paths.iter().copied() {
4513            let path = &self.paths[path_id - 1];
4514            if !path.is_polygon {
4515                continue;
4516            }
4517            if path.is_border {
4518                if path.fwd_poly.is_none() && path.bkd_poly.is_none() {
4519                    self.is_polygon_valid = false;
4520                    return;
4521                }
4522            } else if path.fwd_poly.is_none() || path.bkd_poly.is_none() {
4523                self.is_polygon_valid = false;
4524                return;
4525            }
4526        }
4527    }
4528
4529    fn calc_poly_is_valid(&self, poly_id: usize, leaf_nodes: &[usize]) -> bool {
4530        let Some(poly) = self.polys.get(poly_id.saturating_sub(1)) else {
4531            return false;
4532        };
4533        if poly.is_sub_poly {
4534            return true;
4535        }
4536        if poly.node_locs.len() != poly.ring_nodes.len() {
4537            return false;
4538        }
4539        for (i, node_id) in poly.ring_nodes.iter().copied().enumerate() {
4540            let Some(node) = self.nodes.get(node_id.saturating_sub(1)) else {
4541                return false;
4542            };
4543            if poly.node_locs[i].distance(node.loc) > MOVE_TOL {
4544                return false;
4545            }
4546        }
4547        for path_id in poly.ring_paths.iter().copied() {
4548            let Some(path) = self.paths.get(path_id.saturating_sub(1)) else {
4549                return false;
4550            };
4551            if !path.is_polygon {
4552                return false;
4553            }
4554        }
4555        if !self.poly_is_convex(poly) {
4556            return false;
4557        }
4558        !self.poly_encloses_leaf_node(poly, leaf_nodes)
4559    }
4560
4561    fn poly_is_convex(&self, poly: &Poly) -> bool {
4562        let n = poly.ring_nodes.len();
4563        if n < 3 {
4564            return false;
4565        }
4566        for i in 0..n - 2 {
4567            let Some(p1) = self
4568                .nodes
4569                .get(poly.ring_nodes[i].saturating_sub(1))
4570                .map(|node| node.loc)
4571            else {
4572                return false;
4573            };
4574            let Some(p2) = self
4575                .nodes
4576                .get(poly.ring_nodes[(i + 1) % n].saturating_sub(1))
4577                .map(|node| node.loc)
4578            else {
4579                return false;
4580            };
4581            let Some(p3) = self
4582                .nodes
4583                .get(poly.ring_nodes[(i + 2) % n].saturating_sub(1))
4584                .map(|node| node.loc)
4585            else {
4586                return false;
4587            };
4588            if angle_change(p1, p2, p3) < -CONVEXITY_TOL {
4589                return false;
4590            }
4591        }
4592        true
4593    }
4594
4595    fn poly_encloses_leaf_node(&self, poly: &Poly, leaf_nodes: &[usize]) -> bool {
4596        for node_id in leaf_nodes.iter().copied() {
4597            if poly.ring_nodes.contains(&node_id) {
4598                continue;
4599            }
4600            let Some(node) = self.nodes.get(node_id.saturating_sub(1)) else {
4601                continue;
4602            };
4603            if self.poly_convex_encloses(poly, node.loc) {
4604                return true;
4605            }
4606        }
4607        false
4608    }
4609
4610    fn poly_convex_encloses(&self, poly: &Poly, point: Point) -> bool {
4611        for path_id in poly.ring_paths.iter().copied() {
4612            let Some(path) = self.paths.get(path_id.saturating_sub(1)) else {
4613                return false;
4614            };
4615            let Some((node1, node2)) = path.nodes.first().zip(path.nodes.last()) else {
4616                return false;
4617            };
4618            let Some(p1) = self.nodes.get(node1.saturating_sub(1)).map(|node| node.loc) else {
4619                return false;
4620            };
4621            let Some(p2) = self.nodes.get(node2.saturating_sub(1)).map(|node| node.loc) else {
4622                return false;
4623            };
4624            let mut q = rotate_ccw90(point_sub(p2, p1));
4625            if inner(point_sub(poly.centroid, p1), q) < 0.0 {
4626                q.x *= -1.0;
4627                q.y *= -1.0;
4628            }
4629            if inner(point_sub(point, p1), q) < 0.0 {
4630                return false;
4631            }
4632        }
4633        true
4634    }
4635
4636    fn kill_orphan_vertices_and_creases(&mut self) {
4637        let mut doomed_creases: Vec<usize> = self
4638            .creases
4639            .iter()
4640            .filter(|crease| self.crease_is_orphan(crease))
4641            .map(|crease| crease.index)
4642            .collect();
4643
4644        let doomed_vertices: Vec<usize> = self
4645            .vertices
4646            .iter()
4647            .filter(|vertex| self.vertex_is_orphan(vertex))
4648            .map(|vertex| vertex.index)
4649            .collect();
4650
4651        for crease in &self.creases {
4652            if crease
4653                .vertices
4654                .iter()
4655                .any(|vertex_id| doomed_vertices.contains(vertex_id))
4656            {
4657                doomed_creases.push(crease.index);
4658            }
4659        }
4660        doomed_creases.sort_unstable();
4661        doomed_creases.dedup();
4662
4663        self.delete_creases(&doomed_creases);
4664        self.delete_vertices(&doomed_vertices);
4665    }
4666
4667    fn crease_is_orphan(&self, crease: &Crease) -> bool {
4668        match crease.owner {
4669            OwnerRef::Poly(poly_id) => poly_id == 0 || poly_id > self.polys.len(),
4670            OwnerRef::Path(path_id) => self
4671                .paths
4672                .get(path_id.saturating_sub(1))
4673                .is_none_or(|path| !path.is_sub && !self.path_is_incident_to_filled_poly(path_id)),
4674            _ => true,
4675        }
4676    }
4677
4678    fn vertex_is_orphan(&self, vertex: &Vertex) -> bool {
4679        match vertex.owner {
4680            OwnerRef::Node(node_id) => {
4681                let Some(node) = self.nodes.get(node_id.saturating_sub(1)) else {
4682                    return true;
4683                };
4684                if node.is_sub {
4685                    return false;
4686                }
4687                if !node.is_leaf {
4688                    return true;
4689                }
4690                !node
4691                    .leaf_paths
4692                    .iter()
4693                    .copied()
4694                    .any(|path_id| self.path_is_incident_to_filled_poly(path_id))
4695            }
4696            OwnerRef::Path(path_id) => self
4697                .paths
4698                .get(path_id.saturating_sub(1))
4699                .is_none_or(|path| !path.is_sub && !self.path_is_incident_to_filled_poly(path_id)),
4700            _ => true,
4701        }
4702    }
4703
4704    fn path_is_incident_to_filled_poly(&self, path_id: usize) -> bool {
4705        let Some(path) = self.paths.get(path_id.saturating_sub(1)) else {
4706            return false;
4707        };
4708        path.fwd_poly
4709            .and_then(|poly_id| self.polys.get(poly_id.saturating_sub(1)))
4710            .is_some_and(|poly| !poly.owned_nodes.is_empty())
4711            || path
4712                .bkd_poly
4713                .and_then(|poly_id| self.polys.get(poly_id.saturating_sub(1)))
4714                .is_some_and(|poly| !poly.owned_nodes.is_empty())
4715    }
4716
4717    fn delete_polys(&mut self, doomed: &[usize]) {
4718        if doomed.is_empty() {
4719            return;
4720        }
4721
4722        let mut doomed_flags = ids_to_flags(doomed, self.polys.len());
4723        loop {
4724            let mut changed = false;
4725            for poly in &self.polys {
4726                if doomed_flags[poly.index] {
4727                    continue;
4728                }
4729                if let OwnerRef::Poly(owner) = poly.owner
4730                    && doomed_flags.get(owner).copied().unwrap_or(false)
4731                {
4732                    doomed_flags[poly.index] = true;
4733                    changed = true;
4734                }
4735            }
4736            if !changed {
4737                break;
4738            }
4739        }
4740
4741        let doomed_polys: Vec<usize> = doomed_flags
4742            .iter()
4743            .enumerate()
4744            .skip(1)
4745            .filter_map(|(id, doomed)| doomed.then_some(id))
4746            .collect();
4747        let doomed_facets: Vec<usize> = self
4748            .facets
4749            .iter()
4750            .filter_map(|facet| match facet.owner {
4751                OwnerRef::Poly(poly_id) if doomed_flags.get(poly_id).copied().unwrap_or(false) => {
4752                    Some(facet.index)
4753                }
4754                _ => None,
4755            })
4756            .collect();
4757        let doomed_creases: Vec<usize> = self
4758            .creases
4759            .iter()
4760            .filter_map(|crease| match crease.owner {
4761                OwnerRef::Poly(poly_id) if doomed_flags.get(poly_id).copied().unwrap_or(false) => {
4762                    Some(crease.index)
4763                }
4764                _ => None,
4765            })
4766            .collect();
4767
4768        self.delete_facets(&doomed_facets);
4769        self.delete_creases(&doomed_creases);
4770
4771        let map = keep_map(self.polys.len(), &doomed_polys);
4772        for path in &mut self.paths {
4773            remap_option(&mut path.fwd_poly, &map);
4774            remap_option(&mut path.bkd_poly, &map);
4775        }
4776        for poly in &mut self.polys {
4777            remap_vec(&mut poly.owned_polys, &map);
4778            remap_owner(&mut poly.owner, PartKind::Poly, &map);
4779        }
4780        for node in &mut self.nodes {
4781            remap_owner(&mut node.owner, PartKind::Poly, &map);
4782        }
4783        for path in &mut self.paths {
4784            remap_owner(&mut path.owner, PartKind::Poly, &map);
4785        }
4786        for crease in &mut self.creases {
4787            remap_owner(&mut crease.owner, PartKind::Poly, &map);
4788        }
4789        for facet in &mut self.facets {
4790            remap_owner(&mut facet.owner, PartKind::Poly, &map);
4791        }
4792        remap_vec(&mut self.owned_polys, &map);
4793
4794        self.polys = self
4795            .polys
4796            .drain(..)
4797            .filter(|poly| map[poly.index].is_some())
4798            .enumerate()
4799            .map(|(i, mut poly)| {
4800                poly.index = i + 1;
4801                poly
4802            })
4803            .collect();
4804    }
4805
4806    fn delete_facets(&mut self, doomed: &[usize]) {
4807        if doomed.is_empty() {
4808            return;
4809        }
4810        let map = keep_map(self.facets.len(), doomed);
4811        for crease in &mut self.creases {
4812            remap_option(&mut crease.fwd_facet, &map);
4813            remap_option(&mut crease.bkd_facet, &map);
4814        }
4815        for poly in &mut self.polys {
4816            remap_vec(&mut poly.owned_facets, &map);
4817        }
4818        for facet in &mut self.facets {
4819            remap_vec(&mut facet.head_facets, &map);
4820            remap_vec(&mut facet.tail_facets, &map);
4821        }
4822        self.facets = self
4823            .facets
4824            .drain(..)
4825            .filter(|facet| map[facet.index].is_some())
4826            .enumerate()
4827            .map(|(i, mut facet)| {
4828                facet.index = i + 1;
4829                facet
4830            })
4831            .collect();
4832    }
4833
4834    fn delete_creases(&mut self, doomed: &[usize]) {
4835        if doomed.is_empty() {
4836            return;
4837        }
4838        let map = keep_map(self.creases.len(), doomed);
4839        for vertex in &mut self.vertices {
4840            remap_vec(&mut vertex.creases, &map);
4841        }
4842        for path in &mut self.paths {
4843            remap_vec(&mut path.owned_creases, &map);
4844        }
4845        for poly in &mut self.polys {
4846            remap_vec(&mut poly.local_root_creases, &map);
4847            remap_vec(&mut poly.owned_creases, &map);
4848        }
4849        for facet in &mut self.facets {
4850            remap_vec(&mut facet.creases, &map);
4851        }
4852        self.creases = self
4853            .creases
4854            .drain(..)
4855            .filter(|crease| map[crease.index].is_some())
4856            .enumerate()
4857            .map(|(i, mut crease)| {
4858                crease.index = i + 1;
4859                crease
4860            })
4861            .collect();
4862    }
4863
4864    fn delete_vertices(&mut self, doomed: &[usize]) {
4865        if doomed.is_empty() {
4866            return;
4867        }
4868        let map = keep_map(self.vertices.len(), doomed);
4869        for node in &mut self.nodes {
4870            remap_vec(&mut node.owned_vertices, &map);
4871        }
4872        for path in &mut self.paths {
4873            remap_vec(&mut path.owned_vertices, &map);
4874        }
4875        for poly in &mut self.polys {
4876            remap_vec(&mut poly.local_root_vertices, &map);
4877        }
4878        for vertex in &mut self.vertices {
4879            remap_option(&mut vertex.left_pseudohinge_mate, &map);
4880            remap_option(&mut vertex.right_pseudohinge_mate, &map);
4881        }
4882        for crease in &mut self.creases {
4883            remap_vec(&mut crease.vertices, &map);
4884        }
4885        for facet in &mut self.facets {
4886            remap_vec(&mut facet.vertices, &map);
4887        }
4888        self.vertices = self
4889            .vertices
4890            .drain(..)
4891            .filter(|vertex| map[vertex.index].is_some())
4892            .enumerate()
4893            .map(|(i, mut vertex)| {
4894                vertex.index = i + 1;
4895                vertex
4896            })
4897            .collect();
4898    }
4899
4900    fn promote_first_tree_node_to_root(&mut self) {
4901        let Some(pos) = self.nodes.iter().position(|node| !node.is_sub) else {
4902            return;
4903        };
4904        if pos == 0 {
4905            return;
4906        }
4907        let old_order: Vec<usize> = std::iter::once(pos + 1)
4908            .chain((1..=self.nodes.len()).filter(|id| *id != pos + 1))
4909            .collect();
4910        let mut map = vec![None; self.nodes.len() + 1];
4911        for (new_index, old_index) in old_order.iter().copied().enumerate() {
4912            map[old_index] = Some(new_index + 1);
4913        }
4914
4915        let old_nodes = self.nodes.clone();
4916        self.nodes = old_order
4917            .iter()
4918            .map(|old_index| old_nodes[*old_index - 1].clone())
4919            .collect();
4920
4921        for node in &mut self.nodes {
4922            node.index = map[node.index].expect("node reorder map");
4923            remap_owner(&mut node.owner, PartKind::Node, &map);
4924        }
4925        for edge in &mut self.edges {
4926            remap_vec(&mut edge.nodes, &map);
4927        }
4928        for path in &mut self.paths {
4929            remap_vec(&mut path.nodes, &map);
4930        }
4931        for poly in &mut self.polys {
4932            remap_vec(&mut poly.ring_nodes, &map);
4933            remap_vec(&mut poly.inset_nodes, &map);
4934            remap_vec(&mut poly.owned_nodes, &map);
4935        }
4936        for vertex in &mut self.vertices {
4937            remap_option(&mut vertex.tree_node, &map);
4938            remap_owner(&mut vertex.owner, PartKind::Node, &map);
4939        }
4940        for condition in &mut self.conditions {
4941            condition.kind.remap_nodes(&map);
4942        }
4943        remap_vec(&mut self.owned_nodes, &map);
4944    }
4945
4946    fn renumber_part_indices(&mut self) {
4947        for (i, node) in self.nodes.iter_mut().enumerate() {
4948            node.index = i + 1;
4949        }
4950        for (i, edge) in self.edges.iter_mut().enumerate() {
4951            edge.index = i + 1;
4952        }
4953        for (i, path) in self.paths.iter_mut().enumerate() {
4954            path.index = i + 1;
4955        }
4956        for (i, poly) in self.polys.iter_mut().enumerate() {
4957            poly.index = i + 1;
4958        }
4959        for (i, vertex) in self.vertices.iter_mut().enumerate() {
4960            vertex.index = i + 1;
4961        }
4962        for (i, crease) in self.creases.iter_mut().enumerate() {
4963            crease.index = i + 1;
4964        }
4965        for (i, facet) in self.facets.iter_mut().enumerate() {
4966            facet.index = i + 1;
4967        }
4968        for (i, condition) in self.conditions.iter_mut().enumerate() {
4969            condition.index = i + 1;
4970        }
4971    }
4972
4973    fn clear_crease_pattern_cleanup_data(&mut self) {
4974        for vertex in &mut self.vertices {
4975            vertex.depth = DEPTH_NOT_SET;
4976            vertex.discrete_depth = usize::MAX;
4977        }
4978        for crease in &mut self.creases {
4979            crease.fold = 0;
4980        }
4981        for facet in &mut self.facets {
4982            facet.corridor_edge = None;
4983            facet.head_facets.clear();
4984            facet.tail_facets.clear();
4985            facet.order = usize::MAX;
4986            facet.color = 0;
4987        }
4988    }
4989
4990    fn calc_polygon_filled(&mut self) {
4991        self.is_polygon_filled = false;
4992        if self.owned_polys.is_empty() {
4993            return;
4994        }
4995        for poly_id in self.owned_polys.iter().copied() {
4996            let Some(poly) = self.polys.get(poly_id.saturating_sub(1)) else {
4997                return;
4998            };
4999            if poly.owned_nodes.is_empty() {
5000                return;
5001            }
5002        }
5003        self.is_polygon_filled = true;
5004    }
5005
5006    fn calc_depth_and_bend(&mut self) {
5007        if !self.is_polygon_valid || self.nodes.is_empty() {
5008            return;
5009        }
5010
5011        let root_node = self
5012            .nodes
5013            .iter()
5014            .position(|node| !node.is_sub)
5015            .map(|i| i + 1);
5016        let Some(root_node) = root_node else {
5017            return;
5018        };
5019        self.nodes[root_node - 1].depth = 0.0;
5020        for path_id in self.owned_paths.iter().copied() {
5021            let path = &self.paths[path_id - 1];
5022            if path.nodes.first().copied() == Some(root_node) {
5023                if let Some(back_node) = path.nodes.last().copied() {
5024                    self.nodes[back_node - 1].depth = path.min_paper_length;
5025                }
5026            } else if path.nodes.last().copied() == Some(root_node)
5027                && let Some(front_node) = path.nodes.first().copied()
5028            {
5029                self.nodes[front_node - 1].depth = path.min_paper_length;
5030            }
5031        }
5032
5033        for path in &mut self.paths {
5034            path.min_depth = DEPTH_NOT_SET;
5035            path.min_depth_dist = 0.0;
5036        }
5037
5038        for path_id in 1..=self.paths.len() {
5039            if !self.paths[path_id - 1].is_leaf {
5040                continue;
5041            }
5042            let node_ids = self.paths[path_id - 1].nodes.clone();
5043            let edge_ids = self.paths[path_id - 1].edges.clone();
5044            if node_ids.is_empty() {
5045                continue;
5046            }
5047            let mut min_depth = self.nodes[node_ids[0] - 1].depth;
5048            let mut min_depth_dist = 0.0;
5049            for j in 1..node_ids.len() {
5050                let node_depth = self.nodes[node_ids[j] - 1].depth;
5051                if min_depth > node_depth {
5052                    min_depth = node_depth;
5053                    min_depth_dist +=
5054                        self.edges[edge_ids[j - 1] - 1].strained_length() * self.scale;
5055                }
5056            }
5057            self.paths[path_id - 1].min_depth = min_depth;
5058            self.paths[path_id - 1].min_depth_dist = min_depth_dist;
5059        }
5060
5061        for path_id in 1..=self.paths.len() {
5062            if !self.path_is_gusset(path_id) {
5063                continue;
5064            }
5065            let (max_outset_path, max_front_reduction, _) = self.max_outset_path(path_id);
5066            self.paths[path_id - 1].min_depth = self.paths[max_outset_path - 1].min_depth;
5067            self.paths[path_id - 1].min_depth_dist =
5068                self.paths[max_outset_path - 1].min_depth_dist - max_front_reduction;
5069        }
5070
5071        for vertex in &mut self.vertices {
5072            vertex.depth = DEPTH_NOT_SET;
5073        }
5074
5075        for poly_id in 1..=self.polys.len() {
5076            let nn = self.polys[poly_id - 1].ring_nodes.len();
5077            for j in 0..nn {
5078                let front_node = self.polys[poly_id - 1].ring_nodes[j];
5079                let back_node = self.polys[poly_id - 1].ring_nodes[(j + 1) % nn];
5080                let path_id = self.polys[poly_id - 1].ring_paths[j];
5081                if !(self.path_is_active_axial(path_id) || self.path_is_gusset(path_id)) {
5082                    continue;
5083                }
5084                if let Ok(ridge_vertices) =
5085                    self.get_ridgeline_vertices(poly_id, front_node, back_node)
5086                {
5087                    for vertex_id in ridge_vertices {
5088                        self.set_vertex_depth_from_path(path_id, vertex_id);
5089                    }
5090                }
5091                for vertex_id in self.paths[path_id - 1].owned_vertices.clone() {
5092                    self.set_vertex_depth_from_path(path_id, vertex_id);
5093                }
5094            }
5095        }
5096
5097        for path_id in self.owned_paths.clone() {
5098            if !self.paths[path_id - 1].is_border || self.paths[path_id - 1].is_active {
5099                continue;
5100            }
5101            for vertex_id in self.paths[path_id - 1].owned_vertices.clone() {
5102                for crease_id in self.vertices[vertex_id - 1].creases.clone() {
5103                    if self.crease_is_hinge(crease_id) {
5104                        let ridge_vertex = self.other_crease_vertex(crease_id, vertex_id);
5105                        self.vertices[vertex_id - 1].depth = self.vertices[ridge_vertex - 1].depth;
5106                        break;
5107                    }
5108                }
5109            }
5110        }
5111
5112        for vertex_id in 1..=self.vertices.len() {
5113            if let Some(tree_node) = self.vertices[vertex_id - 1].tree_node {
5114                self.vertices[vertex_id - 1].discrete_depth = self.calc_discrete_depth(tree_node);
5115            } else {
5116                self.vertices[vertex_id - 1].discrete_depth = usize::MAX;
5117            }
5118        }
5119
5120        if self
5121            .vertices
5122            .iter()
5123            .any(|vertex| vertex.depth == DEPTH_NOT_SET)
5124        {
5125            return;
5126        }
5127
5128        for poly_id in self.owned_polys.clone() {
5129            self.calc_poly_bend(poly_id);
5130        }
5131    }
5132
5133    fn set_vertex_depth_from_path(&mut self, path_id: usize, vertex_id: usize) {
5134        let path = &self.paths[path_id - 1];
5135        let p = self.vertices[vertex_id - 1].loc;
5136        let p1 = self.nodes[path.nodes[0] - 1].loc;
5137        let p2 = self.nodes[*path.nodes.last().unwrap() - 1].loc;
5138        let d = inner(point_sub(p, p1), point_sub(p2, p1)) / p2.distance(p1);
5139        self.vertices[vertex_id - 1].depth = if d < path.min_depth_dist {
5140            path.min_depth + path.min_depth_dist - d
5141        } else {
5142            path.min_depth + d - path.min_depth_dist
5143        };
5144    }
5145
5146    fn calc_discrete_depth(&self, node_id: usize) -> usize {
5147        let root_node = self
5148            .nodes
5149            .iter()
5150            .position(|node| !node.is_sub)
5151            .map(|i| i + 1);
5152        if root_node == Some(node_id) {
5153            return 0;
5154        }
5155        let Some(root_node) = root_node else {
5156            return usize::MAX;
5157        };
5158        self.find_any_path_in(&self.owned_paths, root_node, node_id)
5159            .map(|path_id| self.paths[path_id - 1].edges.len())
5160            .unwrap_or(usize::MAX)
5161    }
5162
5163    fn calc_poly_bend(&mut self, poly_id: usize) {
5164        for crease_id in self.polys[poly_id - 1].owned_creases.clone() {
5165            self.calc_crease_bend(crease_id);
5166        }
5167
5168        let mut all_vertices = Vec::new();
5169        for crease_id in self.polys[poly_id - 1].owned_creases.clone() {
5170            for vertex_id in self.creases[crease_id - 1].vertices.clone() {
5171                push_unique(&mut all_vertices, vertex_id);
5172            }
5173        }
5174        for node_id in self.polys[poly_id - 1].ring_nodes.clone() {
5175            if let Some(vertex_id) = self.nodes[node_id - 1].owned_vertices.first().copied() {
5176                push_unique(&mut all_vertices, vertex_id);
5177            }
5178        }
5179
5180        self.polys[poly_id - 1].local_root_vertices.clear();
5181        self.polys[poly_id - 1].local_root_creases.clear();
5182        let mut min_discrete_depth = usize::MAX;
5183        for vertex_id in all_vertices {
5184            let discrete_depth = self.vertices[vertex_id - 1].discrete_depth;
5185            if min_discrete_depth > discrete_depth {
5186                min_discrete_depth = discrete_depth;
5187                self.polys[poly_id - 1].local_root_vertices.clear();
5188                self.polys[poly_id - 1].local_root_creases.clear();
5189            }
5190            if min_discrete_depth == discrete_depth {
5191                push_unique(&mut self.polys[poly_id - 1].local_root_vertices, vertex_id);
5192                for crease_id in self.vertices[vertex_id - 1].creases.clone() {
5193                    if self.crease_is_hinge(crease_id)
5194                        && self.polys[poly_id - 1].owned_creases.contains(&crease_id)
5195                    {
5196                        push_unique(&mut self.polys[poly_id - 1].local_root_creases, crease_id);
5197                    }
5198                }
5199            }
5200        }
5201    }
5202
5203    fn calc_crease_bend(&mut self, crease_id: usize) {
5204        if !self.crease_is_hinge(crease_id)
5205            || self.creases[crease_id - 1].kind == CREASE_PSEUDOHINGE
5206        {
5207            return;
5208        }
5209        let v0 = self.creases[crease_id - 1].vertices[0];
5210        let v1 = self.creases[crease_id - 1].vertices[1];
5211        let bottom = if self.vertices[v0 - 1].elevation > self.vertices[v1 - 1].elevation {
5212            v1
5213        } else {
5214            v0
5215        };
5216        let ag_creases: Vec<usize> = self.vertices[bottom - 1]
5217            .creases
5218            .iter()
5219            .copied()
5220            .filter(|id| self.crease_is_axial_or_gusset(*id))
5221            .take(2)
5222            .collect();
5223        if ag_creases.len() < 2 {
5224            return;
5225        }
5226        let vertex1 = self.other_crease_vertex(ag_creases[0], bottom);
5227        let vertex3 = self.other_crease_vertex(ag_creases[1], bottom);
5228        let depth1 = self.vertices[vertex1 - 1].depth;
5229        let depth2 = self.vertices[bottom - 1].depth;
5230        let depth3 = self.vertices[vertex3 - 1].depth;
5231        if (depth1 > depth2 && depth2 < depth3) || (depth1 < depth2 && depth2 > depth3) {
5232            self.creases[crease_id - 1].kind = CREASE_FOLDED_HINGE;
5233        } else {
5234            self.creases[crease_id - 1].kind = CREASE_UNFOLDED_HINGE;
5235        }
5236    }
5237
5238    fn calc_vertex_depth_validity(&mut self) {
5239        self.is_vertex_depth_valid = false;
5240        if self.vertices.is_empty() {
5241            return;
5242        }
5243        if self
5244            .vertices
5245            .iter()
5246            .any(|vertex| vertex.depth == DEPTH_NOT_SET)
5247        {
5248            return;
5249        }
5250        self.is_vertex_depth_valid = true;
5251    }
5252
5253    fn calc_facet_data_validity(&mut self) {
5254        self.is_facet_data_valid = false;
5255        if self.facets.is_empty() {
5256            return;
5257        }
5258        if self.facets.iter().any(|facet| !facet.is_well_formed) {
5259            return;
5260        }
5261        if self
5262            .vertices
5263            .iter()
5264            .any(|vertex| !vertex.is_border && vertex.creases.len() % 2 != 0)
5265        {
5266            return;
5267        }
5268        self.is_facet_data_valid = true;
5269    }
5270
5271    fn calc_facet_corridor_edges(&mut self) {
5272        for poly_id in self.owned_polys.clone() {
5273            self.calc_poly_facet_corridor_edges(poly_id);
5274        }
5275    }
5276
5277    fn calc_poly_facet_corridor_edges(&mut self, poly_id: usize) {
5278        for facet_id in self.polys[poly_id - 1].owned_facets.clone() {
5279            if self.facets[facet_id - 1].corridor_edge.is_some() {
5280                continue;
5281            }
5282            if !self.facet_is_axial(facet_id) {
5283                continue;
5284            }
5285            let Some(bottom_crease) = self.facet_bottom_crease(facet_id) else {
5286                continue;
5287            };
5288            let vertices = self.creases[bottom_crease - 1].vertices.clone();
5289            if vertices.len() < 2 {
5290                continue;
5291            }
5292            let (Some(node1), Some(node2)) = (
5293                self.vertices[vertices[0] - 1].tree_node,
5294                self.vertices[vertices[1] - 1].tree_node,
5295            ) else {
5296                continue;
5297            };
5298            let Some(edge_id) = self.edge_between_nodes(node1, node2) else {
5299                continue;
5300            };
5301            self.set_facet_corridor_edge(poly_id, facet_id, edge_id);
5302        }
5303    }
5304
5305    fn set_facet_corridor_edge(&mut self, poly_id: usize, facet_id: usize, edge_id: usize) {
5306        self.facets[facet_id - 1].corridor_edge = Some(edge_id);
5307        let crease_ids = self.facets[facet_id - 1].creases.clone();
5308        for crease_id in crease_ids {
5309            if self.crease_is_regular_hinge(crease_id) {
5310                continue;
5311            }
5312            let Some(other_facet) = self.crease_other_facet(crease_id, facet_id) else {
5313                continue;
5314            };
5315            if !self.polys[poly_id - 1].owned_facets.contains(&other_facet) {
5316                continue;
5317            }
5318            if self.facets[other_facet - 1].corridor_edge.is_some() {
5319                continue;
5320            }
5321            self.set_facet_corridor_edge(poly_id, other_facet, edge_id);
5322        }
5323    }
5324
5325    fn edge_between_nodes(&self, node1: usize, node2: usize) -> Option<usize> {
5326        self.nodes
5327            .get(node1.saturating_sub(1))?
5328            .edges
5329            .iter()
5330            .copied()
5331            .find(|edge_id| {
5332                self.edges
5333                    .get(edge_id.saturating_sub(1))
5334                    .is_some_and(|edge| edge.nodes.contains(&node2))
5335            })
5336    }
5337
5338    fn calc_facet_order(&mut self) {
5339        let mut root_networks = self.calc_root_networks();
5340        let num_depth_zero = root_networks
5341            .iter()
5342            .filter(|network| network.discrete_depth == 0)
5343            .count();
5344
5345        self.is_local_root_connectable = true;
5346        for network in &root_networks {
5347            if network.discrete_depth != 0 && !network.is_connectable {
5348                self.is_local_root_connectable = false;
5349            }
5350        }
5351        self.is_local_root_connectable &= num_depth_zero == 1;
5352        if !self.is_local_root_connectable {
5353            return;
5354        }
5355
5356        for network in &root_networks {
5357            self.connect_facet_graph(network);
5358        }
5359
5360        let Some(global_index) = root_networks
5361            .iter()
5362            .position(|network| network.discrete_depth == 0)
5363        else {
5364            self.is_local_root_connectable = false;
5365            return;
5366        };
5367        let mut global_root_network = root_networks.remove(global_index);
5368
5369        while !root_networks.is_empty() {
5370            let mut absorbed_index = None;
5371            let mut absorbed_vertex = None;
5372            for (i, network) in root_networks.iter().enumerate() {
5373                if let Some(vertex_id) = self.root_network_can_absorb(&global_root_network, network)
5374                {
5375                    absorbed_index = Some(i);
5376                    absorbed_vertex = Some(vertex_id);
5377                    break;
5378                }
5379            }
5380            let (Some(index), Some(vertex_id)) = (absorbed_index, absorbed_vertex) else {
5381                self.is_local_root_connectable = false;
5382                return;
5383            };
5384            self.vertex_swap_links(vertex_id);
5385            let network = root_networks.remove(index);
5386            for poly_id in network.cc_polys {
5387                push_unique(&mut global_root_network.cc_polys, poly_id);
5388            }
5389        }
5390
5391        self.root_network_break_one_link(&global_root_network);
5392
5393        for facet in &mut self.facets {
5394            facet.order = usize::MAX;
5395        }
5396        let source_facet = self
5397            .facets
5398            .iter()
5399            .find(|facet| self.facet_is_source(facet.index))
5400            .map(|facet| facet.index);
5401        let Some(source_facet) = source_facet else {
5402            self.is_local_root_connectable = false;
5403            return;
5404        };
5405        let mut next_order = 0;
5406        self.calc_facet_order_recursive(source_facet, &mut next_order);
5407    }
5408
5409    fn calc_root_networks(&mut self) -> Vec<RootNetwork> {
5410        for poly_id in self.owned_polys.clone() {
5411            self.calc_local_facet_order(poly_id);
5412        }
5413
5414        let mut local_root_vertices = Vec::new();
5415        let mut local_root_creases = Vec::new();
5416        for poly_id in self.owned_polys.clone() {
5417            for vertex_id in self.polys[poly_id - 1].local_root_vertices.clone() {
5418                push_unique(&mut local_root_vertices, vertex_id);
5419            }
5420            for crease_id in self.polys[poly_id - 1].local_root_creases.clone() {
5421                push_unique(&mut local_root_creases, crease_id);
5422            }
5423        }
5424
5425        for vertex in &mut self.vertices {
5426            vertex.cc_flag = ROOT_FLAG_INELIGIBLE;
5427            vertex.st_flag = ROOT_FLAG_INELIGIBLE;
5428        }
5429        for crease in &mut self.creases {
5430            crease.cc_flag = ROOT_FLAG_INELIGIBLE;
5431            crease.st_flag = ROOT_FLAG_INELIGIBLE;
5432        }
5433
5434        for vertex_id in local_root_vertices.iter().copied() {
5435            self.vertices[vertex_id - 1].cc_flag = ROOT_FLAG_NOT_YET;
5436            self.vertices[vertex_id - 1].st_flag = ROOT_FLAG_NOT_YET;
5437        }
5438        for crease_id in local_root_creases.iter().copied() {
5439            self.creases[crease_id - 1].cc_flag = ROOT_FLAG_NOT_YET;
5440            self.creases[crease_id - 1].st_flag = ROOT_FLAG_NOT_YET;
5441        }
5442
5443        let mut root_networks = Vec::new();
5444        for vertex_id in local_root_vertices {
5445            if root_networks
5446                .iter()
5447                .any(|network: &RootNetwork| network.cc_vertices.contains(&vertex_id))
5448            {
5449                continue;
5450            }
5451            let discrete_depth = self.vertices[vertex_id - 1].discrete_depth;
5452            let mut network = RootNetwork::new(discrete_depth);
5453            self.try_add_vertex_to_connected_component(&mut network, vertex_id);
5454            root_networks.push(network);
5455        }
5456
5457        for network in &mut root_networks {
5458            if let Some(vertex_id) = network.cc_vertices.first().copied() {
5459                self.try_add_vertex_to_spanning_tree(network, vertex_id);
5460            }
5461        }
5462
5463        for network in &mut root_networks {
5464            self.classify_root_network_vertices(network);
5465        }
5466
5467        root_networks
5468    }
5469
5470    fn why_not_local_root_connectable(&self) -> (Vec<usize>, Vec<usize>) {
5471        let mut tree = self.clone();
5472        let root_networks = tree.calc_root_networks();
5473        let mut bad_vertices = Vec::new();
5474        let mut bad_creases = Vec::new();
5475        let mut zero_depth_network: Option<usize> = None;
5476
5477        for (network_index, network) in root_networks.iter().enumerate() {
5478            if network.discrete_depth == 0 {
5479                if let Some(zero_index) = zero_depth_network {
5480                    let zero_network = &root_networks[zero_index];
5481                    for vertex_id in network.cc_vertices.iter().copied() {
5482                        push_unique(&mut bad_vertices, vertex_id);
5483                    }
5484                    for crease_id in network.cc_creases.iter().copied() {
5485                        push_unique(&mut bad_creases, crease_id);
5486                    }
5487                    for vertex_id in zero_network.cc_vertices.iter().copied() {
5488                        push_unique(&mut bad_vertices, vertex_id);
5489                    }
5490                    for crease_id in zero_network.cc_creases.iter().copied() {
5491                        push_unique(&mut bad_creases, crease_id);
5492                    }
5493                } else {
5494                    zero_depth_network = Some(network_index);
5495                }
5496            } else if !network.is_connectable {
5497                for vertex_id in network.cc_vertices.iter().copied() {
5498                    push_unique(&mut bad_vertices, vertex_id);
5499                }
5500                for crease_id in network.cc_creases.iter().copied() {
5501                    push_unique(&mut bad_creases, crease_id);
5502                }
5503            }
5504        }
5505
5506        (bad_vertices, bad_creases)
5507    }
5508
5509    fn calc_local_facet_order(&mut self, poly_id: usize) {
5510        for facet_id in self.polys[poly_id - 1].owned_facets.clone() {
5511            self.facets[facet_id - 1].head_facets.clear();
5512            self.facets[facet_id - 1].tail_facets.clear();
5513        }
5514
5515        let Some(start_vertex) = self.polys[poly_id - 1]
5516            .local_root_vertices
5517            .iter()
5518            .copied()
5519            .find(|vertex_id| self.vertex_is_axial(*vertex_id))
5520        else {
5521            return;
5522        };
5523        let Some(start_crease) = self.incident_interior_crease(poly_id, start_vertex) else {
5524            return;
5525        };
5526        let Some(start_facet) = self.crease_right_non_pseudohinge_facet(start_crease) else {
5527            return;
5528        };
5529
5530        let mut cur_facet = start_facet;
5531        let mut guard = 0;
5532        loop {
5533            let Some(next_facet) = self.facet_right_non_pseudohinge_facet(cur_facet) else {
5534                return;
5535            };
5536            self.facet_link_to(cur_facet, next_facet);
5537            let Some(bottom_crease) = self.facet_bottom_crease(cur_facet) else {
5538                return;
5539            };
5540            self.build_corridor_links(bottom_crease, cur_facet);
5541            cur_facet = next_facet;
5542            if cur_facet == start_facet {
5543                break;
5544            }
5545            guard += 1;
5546            if guard > self.facets.len().saturating_mul(4).max(100) {
5547                return;
5548            }
5549        }
5550    }
5551
5552    fn incident_interior_crease(&self, poly_id: usize, vertex_id: usize) -> Option<usize> {
5553        self.vertices[vertex_id - 1]
5554            .creases
5555            .iter()
5556            .copied()
5557            .find(|crease_id| {
5558                (self.crease_is_hinge(*crease_id)
5559                    || self.creases[*crease_id - 1].kind == CREASE_RIDGE)
5560                    && self.polys[poly_id - 1].owned_creases.contains(crease_id)
5561            })
5562    }
5563
5564    fn build_corridor_links(&mut self, from_crease: usize, from_facet: usize) {
5565        let Some(bottom_crease) = self.facet_bottom_crease(from_facet) else {
5566            return;
5567        };
5568        if bottom_crease == from_crease {
5569            for next_crease in self.facets[from_facet - 1].creases.clone() {
5570                if self.creases[next_crease - 1].kind != CREASE_RIDGE {
5571                    continue;
5572                }
5573                let Some(next_facet) = self.crease_other_facet(next_crease, from_facet) else {
5574                    continue;
5575                };
5576                if self.creases[bottom_crease - 1].kind == CREASE_AXIAL {
5577                    if self.facet_left_facet(from_facet) == Some(next_facet) {
5578                        continue;
5579                    }
5580                    if self.facet_right_facet(from_facet) == Some(next_facet) {
5581                        continue;
5582                    }
5583                }
5584                if self.facets_are_linked(from_facet, next_facet) {
5585                    continue;
5586                }
5587                self.facet_link_to(from_facet, next_facet);
5588                self.build_corridor_links(next_crease, next_facet);
5589            }
5590        } else if self.creases[bottom_crease - 1].kind == CREASE_GUSSET {
5591            let Some(next_facet) = self.crease_other_facet(bottom_crease, from_facet) else {
5592                return;
5593            };
5594            self.facet_link_to(from_facet, next_facet);
5595            self.build_corridor_links(bottom_crease, next_facet);
5596        } else {
5597            if !self.facet_is_pseudohinge(from_facet) {
5598                return;
5599            }
5600            let Some(mut ph_crease) = self.facet_left_crease(from_facet) else {
5601                return;
5602            };
5603            let next_facet = if self.creases[ph_crease - 1].kind == CREASE_PSEUDOHINGE {
5604                self.crease_left_facet(ph_crease)
5605            } else {
5606                let Some(right_crease) = self.facet_right_crease(from_facet) else {
5607                    return;
5608                };
5609                ph_crease = right_crease;
5610                if self.creases[ph_crease - 1].kind != CREASE_PSEUDOHINGE {
5611                    return;
5612                }
5613                self.crease_right_facet(ph_crease)
5614            };
5615            let Some(next_facet) = next_facet else {
5616                return;
5617            };
5618            self.facet_link_to(from_facet, next_facet);
5619            let Some(next_bottom) = self.facet_bottom_crease(next_facet) else {
5620                return;
5621            };
5622            self.build_corridor_links(next_bottom, next_facet);
5623        }
5624    }
5625
5626    fn try_add_vertex_to_connected_component(
5627        &mut self,
5628        network: &mut RootNetwork,
5629        vertex_id: usize,
5630    ) {
5631        if self.vertices[vertex_id - 1].cc_flag == ROOT_FLAG_INELIGIBLE {
5632            return;
5633        }
5634        if self.vertices[vertex_id - 1].cc_flag == ROOT_FLAG_ALREADY_ADDED {
5635            return;
5636        }
5637        self.vertices[vertex_id - 1].cc_flag = ROOT_FLAG_ALREADY_ADDED;
5638        network.cc_vertices.push(vertex_id);
5639
5640        for crease_id in self.vertices[vertex_id - 1].creases.clone() {
5641            self.try_add_crease_to_connected_component(network, crease_id);
5642        }
5643        if let Some(mate_vertex) = self.vertices[vertex_id - 1].left_pseudohinge_mate {
5644            self.try_add_vertex_to_connected_component(network, mate_vertex);
5645        }
5646        if let Some(mate_vertex) = self.vertices[vertex_id - 1].right_pseudohinge_mate {
5647            self.try_add_vertex_to_connected_component(network, mate_vertex);
5648        }
5649
5650        if let Some(node_id) = self.vertices[vertex_id - 1].tree_node
5651            && self.nodes[node_id - 1].is_leaf
5652        {
5653            for crease_id in self.vertices[vertex_id - 1].creases.clone() {
5654                if self.creases[crease_id - 1].kind != CREASE_RIDGE {
5655                    continue;
5656                }
5657                if let OwnerRef::Poly(poly_id) = self.creases[crease_id - 1].owner {
5658                    push_unique(&mut network.cc_polys, poly_id);
5659                }
5660            }
5661        }
5662    }
5663
5664    fn try_add_crease_to_connected_component(
5665        &mut self,
5666        network: &mut RootNetwork,
5667        crease_id: usize,
5668    ) {
5669        if !self.crease_is_hinge(crease_id) {
5670            return;
5671        }
5672        if self.creases[crease_id - 1].cc_flag == ROOT_FLAG_INELIGIBLE {
5673            return;
5674        }
5675        if self.creases[crease_id - 1].cc_flag == ROOT_FLAG_ALREADY_ADDED {
5676            return;
5677        }
5678        self.creases[crease_id - 1].cc_flag = ROOT_FLAG_ALREADY_ADDED;
5679        network.cc_creases.push(crease_id);
5680        if let OwnerRef::Poly(poly_id) = self.creases[crease_id - 1].owner {
5681            push_unique(&mut network.cc_polys, poly_id);
5682        }
5683        let vertices = self.creases[crease_id - 1].vertices.clone();
5684        for vertex_id in vertices {
5685            self.try_add_vertex_to_connected_component(network, vertex_id);
5686        }
5687    }
5688
5689    fn try_add_vertex_to_spanning_tree(&mut self, network: &mut RootNetwork, vertex_id: usize) {
5690        if self.vertices[vertex_id - 1].st_flag == ROOT_FLAG_INELIGIBLE {
5691            return;
5692        }
5693        if self.vertices[vertex_id - 1].st_flag == ROOT_FLAG_ALREADY_ADDED {
5694            return;
5695        }
5696        self.vertices[vertex_id - 1].st_flag = ROOT_FLAG_ALREADY_ADDED;
5697        network.st_vertices.push(vertex_id);
5698
5699        for crease_id in self.vertices[vertex_id - 1].creases.clone() {
5700            self.try_add_crease_to_spanning_tree(network, crease_id);
5701        }
5702        if let Some(mate_vertex) = self.vertices[vertex_id - 1].left_pseudohinge_mate {
5703            self.try_add_vertex_to_spanning_tree(network, mate_vertex);
5704        }
5705        if let Some(mate_vertex) = self.vertices[vertex_id - 1].right_pseudohinge_mate {
5706            self.try_add_vertex_to_spanning_tree(network, mate_vertex);
5707        }
5708    }
5709
5710    fn try_add_crease_to_spanning_tree(&mut self, network: &mut RootNetwork, crease_id: usize) {
5711        if !self.crease_is_hinge(crease_id) {
5712            return;
5713        }
5714        if self.creases[crease_id - 1].cc_flag == ROOT_FLAG_INELIGIBLE {
5715            return;
5716        }
5717        if self.creases[crease_id - 1].st_flag == ROOT_FLAG_ALREADY_ADDED {
5718            return;
5719        }
5720        let v1 = self.creases[crease_id - 1].vertices[0];
5721        let v2 = self.creases[crease_id - 1].vertices[1];
5722        let c1 = self.vertices[v1 - 1].st_flag == ROOT_FLAG_ALREADY_ADDED;
5723        let c2 = self.vertices[v2 - 1].st_flag == ROOT_FLAG_ALREADY_ADDED;
5724        if c1 && c2 {
5725            return;
5726        }
5727        self.creases[crease_id - 1].st_flag = ROOT_FLAG_ALREADY_ADDED;
5728        network.st_creases.push(crease_id);
5729        if !c1 {
5730            self.try_add_vertex_to_spanning_tree(network, v1);
5731        }
5732        if !c2 {
5733            self.try_add_vertex_to_spanning_tree(network, v2);
5734        }
5735    }
5736
5737    fn classify_root_network_vertices(&self, network: &mut RootNetwork) {
5738        for vertex_id in network.cc_vertices.iter().copied() {
5739            if !self.vertex_is_axial(vertex_id) {
5740                continue;
5741            }
5742            let cc_degree = self.vertex_degree(vertex_id, &network.cc_creases);
5743            let st_degree = self.vertex_degree(vertex_id, &network.st_creases);
5744            match cc_degree {
5745                0 => network.cc0.push(vertex_id),
5746                1 => network.cc1.push(vertex_id),
5747                2 if st_degree == 1 => network.cc2_st1.push(vertex_id),
5748                2 if st_degree == 2 => network.cc2_st2.push(vertex_id),
5749                _ => {}
5750            }
5751            let axial_degree = self.vertex_num_hinge_creases(vertex_id);
5752            network.is_connectable |= axial_degree == 2 && cc_degree == 1;
5753        }
5754    }
5755
5756    fn connect_facet_graph(&mut self, network: &RootNetwork) {
5757        if let Some(vertex_id) = network.cc0.first().copied() {
5758            for crease_id in self.vertices[vertex_id - 1].creases.clone() {
5759                if self.creases[crease_id - 1].kind == CREASE_RIDGE
5760                    && let (Some(fwd), Some(bkd)) = (
5761                        self.creases[crease_id - 1].fwd_facet,
5762                        self.creases[crease_id - 1].bkd_facet,
5763                    )
5764                {
5765                    self.facet_unlink(fwd, bkd);
5766                }
5767            }
5768
5769            let mut needs_skip = !self.vertices[vertex_id - 1].is_border;
5770            for crease_id in self.vertices[vertex_id - 1].creases.clone() {
5771                if self.crease_is_border(crease_id)
5772                    || self.creases[crease_id - 1].kind != CREASE_AXIAL
5773                {
5774                    continue;
5775                }
5776                if needs_skip {
5777                    needs_skip = false;
5778                } else if let (Some(fwd), Some(bkd)) = (
5779                    self.creases[crease_id - 1].fwd_facet,
5780                    self.creases[crease_id - 1].bkd_facet,
5781                ) {
5782                    self.facet_link(fwd, bkd);
5783                }
5784            }
5785            return;
5786        }
5787
5788        for vertex_id in network.cc2_st2.iter().copied() {
5789            self.vertex_swap_links(vertex_id);
5790        }
5791    }
5792
5793    fn root_network_can_absorb(
5794        &self,
5795        global_network: &RootNetwork,
5796        network: &RootNetwork,
5797    ) -> Option<usize> {
5798        for poly_id in global_network.cc_polys.iter().copied() {
5799            for path_id in self.polys[poly_id - 1].ring_paths.iter().copied() {
5800                for vertex_id in self.paths[path_id - 1].owned_vertices.iter().copied() {
5801                    if self.vertices[vertex_id - 1].discrete_depth != network.discrete_depth {
5802                        continue;
5803                    }
5804                    if network.cc1.contains(&vertex_id) {
5805                        return Some(vertex_id);
5806                    }
5807                }
5808            }
5809        }
5810        None
5811    }
5812
5813    fn root_network_break_one_link(&mut self, network: &RootNetwork) {
5814        if !network.cc0.is_empty() {
5815            return;
5816        }
5817        if let Some(vertex_id) = network.cc1.first().copied() {
5818            let Some(crease_id) = self.vertex_hinge_crease(vertex_id) else {
5819                return;
5820            };
5821            let (Some(left), Some(right)) = (
5822                self.crease_left_non_pseudohinge_facet(crease_id),
5823                self.crease_right_non_pseudohinge_facet(crease_id),
5824            ) else {
5825                return;
5826            };
5827            self.facet_unlink(left, right);
5828            return;
5829        }
5830
5831        let Some(vertex_id) = network.cc2_st1.first().copied() else {
5832            return;
5833        };
5834        let hinges = self.vertex_hinge_creases(vertex_id);
5835        let Some(crease_id) = hinges.first().copied() else {
5836            return;
5837        };
5838        if let (Some(fwd), Some(bkd)) = (
5839            self.creases[crease_id - 1].fwd_facet,
5840            self.creases[crease_id - 1].bkd_facet,
5841        ) {
5842            self.facet_unlink(fwd, bkd);
5843        }
5844    }
5845
5846    fn calc_facet_order_recursive(&mut self, facet_id: usize, next_order: &mut usize) {
5847        if self.facets[facet_id - 1].order != usize::MAX {
5848            return;
5849        }
5850        if self.facets[facet_id - 1]
5851            .tail_facets
5852            .iter()
5853            .any(|tail| self.facets[*tail - 1].order == usize::MAX)
5854        {
5855            return;
5856        }
5857        self.facets[facet_id - 1].order = *next_order;
5858        *next_order += 1;
5859        for head_facet in self.facets[facet_id - 1].head_facets.clone() {
5860            self.calc_facet_order_recursive(head_facet, next_order);
5861        }
5862    }
5863
5864    fn calc_facet_color(&mut self) {
5865        let mut source_facet = None;
5866        for facet_id in 1..=self.facets.len() {
5867            self.facets[facet_id - 1].color = FACET_NOT_ORIENTED;
5868            if self.facet_is_source(facet_id) {
5869                source_facet = Some(facet_id);
5870            }
5871        }
5872        if let Some(source_facet) = source_facet {
5873            self.calc_facet_color_recursive(source_facet, FACET_COLOR_UP);
5874        }
5875    }
5876
5877    fn calc_facet_color_recursive(&mut self, facet_id: usize, color: i32) {
5878        if self.facets[facet_id - 1].color != FACET_NOT_ORIENTED {
5879            return;
5880        }
5881        self.facets[facet_id - 1].color = color;
5882        for crease_id in self.facets[facet_id - 1].creases.clone() {
5883            let Some(other_facet) = self.crease_other_facet(crease_id, facet_id) else {
5884                continue;
5885            };
5886            if self.facets[other_facet - 1].color != FACET_NOT_ORIENTED {
5887                continue;
5888            }
5889            let other_color = match self.creases[crease_id - 1].kind {
5890                CREASE_AXIAL | CREASE_GUSSET | CREASE_RIDGE | CREASE_FOLDED_HINGE
5891                | CREASE_PSEUDOHINGE => opposite_facet_color(color),
5892                CREASE_UNFOLDED_HINGE => color,
5893                _ => continue,
5894            };
5895            self.calc_facet_color_recursive(other_facet, other_color);
5896        }
5897    }
5898
5899    fn calc_fold_directions(&mut self) {
5900        for crease_id in 1..=self.creases.len() {
5901            self.calc_crease_fold(crease_id);
5902        }
5903    }
5904
5905    fn calc_crease_fold(&mut self, crease_id: usize) {
5906        let (Some(fwd), Some(bkd)) = (
5907            self.creases[crease_id - 1].fwd_facet,
5908            self.creases[crease_id - 1].bkd_facet,
5909        ) else {
5910            self.creases[crease_id - 1].fold = FOLD_BORDER;
5911            return;
5912        };
5913        let fwd_facet = &self.facets[fwd - 1];
5914        let bkd_facet = &self.facets[bkd - 1];
5915        self.creases[crease_id - 1].fold = if fwd_facet.color == bkd_facet.color {
5916            FOLD_FLAT
5917        } else if fwd_facet.color == FACET_COLOR_UP {
5918            if fwd_facet.order > bkd_facet.order {
5919                FOLD_MOUNTAIN
5920            } else {
5921                FOLD_VALLEY
5922            }
5923        } else if fwd_facet.order > bkd_facet.order {
5924            FOLD_VALLEY
5925        } else {
5926            FOLD_MOUNTAIN
5927        };
5928    }
5929
5930    fn other_crease_vertex(&self, crease_id: usize, vertex_id: usize) -> usize {
5931        let crease = &self.creases[crease_id - 1];
5932        if crease.vertices[0] == vertex_id {
5933            crease.vertices[1]
5934        } else {
5935            crease.vertices[0]
5936        }
5937    }
5938
5939    fn crease_is_hinge(&self, crease_id: usize) -> bool {
5940        matches!(
5941            self.creases[crease_id - 1].kind,
5942            CREASE_UNFOLDED_HINGE | CREASE_FOLDED_HINGE | CREASE_PSEUDOHINGE
5943        )
5944    }
5945
5946    fn crease_is_regular_hinge(&self, crease_id: usize) -> bool {
5947        matches!(
5948            self.creases[crease_id - 1].kind,
5949            CREASE_UNFOLDED_HINGE | CREASE_FOLDED_HINGE
5950        )
5951    }
5952
5953    fn crease_is_border(&self, crease_id: usize) -> bool {
5954        let crease = &self.creases[crease_id - 1];
5955        crease.fwd_facet.is_none() || crease.bkd_facet.is_none()
5956    }
5957
5958    fn crease_other_facet(&self, crease_id: usize, facet_id: usize) -> Option<usize> {
5959        let crease = &self.creases[crease_id - 1];
5960        if crease.fwd_facet == Some(facet_id) {
5961            crease.bkd_facet
5962        } else if crease.bkd_facet == Some(facet_id) {
5963            crease.fwd_facet
5964        } else {
5965            None
5966        }
5967    }
5968
5969    fn crease_left_facet(&self, crease_id: usize) -> Option<usize> {
5970        let crease = &self.creases[crease_id - 1];
5971        if let Some(fwd) = crease.fwd_facet
5972            && self.facet_right_crease(fwd) == Some(crease_id)
5973        {
5974            return Some(fwd);
5975        }
5976        if let Some(bkd) = crease.bkd_facet
5977            && self.facet_right_crease(bkd) == Some(crease_id)
5978        {
5979            return Some(bkd);
5980        }
5981        None
5982    }
5983
5984    fn crease_right_facet(&self, crease_id: usize) -> Option<usize> {
5985        let crease = &self.creases[crease_id - 1];
5986        if let Some(fwd) = crease.fwd_facet
5987            && self.facet_left_crease(fwd) == Some(crease_id)
5988        {
5989            return Some(fwd);
5990        }
5991        if let Some(bkd) = crease.bkd_facet
5992            && self.facet_left_crease(bkd) == Some(crease_id)
5993        {
5994            return Some(bkd);
5995        }
5996        None
5997    }
5998
5999    fn crease_left_non_pseudohinge_facet(&self, crease_id: usize) -> Option<usize> {
6000        let mut facet_id = self.crease_left_facet(crease_id)?;
6001        let mut guard = 0;
6002        while self.facet_is_pseudohinge(facet_id) {
6003            facet_id = self.facet_left_facet(facet_id)?;
6004            guard += 1;
6005            if guard > self.facets.len() {
6006                return None;
6007            }
6008        }
6009        Some(facet_id)
6010    }
6011
6012    fn crease_right_non_pseudohinge_facet(&self, crease_id: usize) -> Option<usize> {
6013        let mut facet_id = self.crease_right_facet(crease_id)?;
6014        let mut guard = 0;
6015        while self.facet_is_pseudohinge(facet_id) {
6016            facet_id = self.facet_right_facet(facet_id)?;
6017            guard += 1;
6018            if guard > self.facets.len() {
6019                return None;
6020            }
6021        }
6022        Some(facet_id)
6023    }
6024
6025    fn facet_bottom_crease(&self, facet_id: usize) -> Option<usize> {
6026        self.facets
6027            .get(facet_id.saturating_sub(1))?
6028            .creases
6029            .first()
6030            .copied()
6031    }
6032
6033    fn facet_left_crease(&self, facet_id: usize) -> Option<usize> {
6034        self.facets
6035            .get(facet_id.saturating_sub(1))?
6036            .creases
6037            .last()
6038            .copied()
6039    }
6040
6041    fn facet_right_crease(&self, facet_id: usize) -> Option<usize> {
6042        self.facets
6043            .get(facet_id.saturating_sub(1))?
6044            .creases
6045            .get(1)
6046            .copied()
6047    }
6048
6049    fn facet_is_axial(&self, facet_id: usize) -> bool {
6050        self.facet_bottom_crease(facet_id)
6051            .is_some_and(|crease_id| self.creases[crease_id - 1].kind == CREASE_AXIAL)
6052    }
6053
6054    fn facet_is_pseudohinge(&self, facet_id: usize) -> bool {
6055        self.facet_left_crease(facet_id)
6056            .is_some_and(|crease_id| self.creases[crease_id - 1].kind == CREASE_PSEUDOHINGE)
6057            || self
6058                .facet_right_crease(facet_id)
6059                .is_some_and(|crease_id| self.creases[crease_id - 1].kind == CREASE_PSEUDOHINGE)
6060    }
6061
6062    fn facet_left_facet(&self, facet_id: usize) -> Option<usize> {
6063        self.crease_left_facet(self.facet_left_crease(facet_id)?)
6064    }
6065
6066    fn facet_right_facet(&self, facet_id: usize) -> Option<usize> {
6067        self.crease_right_facet(self.facet_right_crease(facet_id)?)
6068    }
6069
6070    fn facet_right_non_pseudohinge_facet(&self, facet_id: usize) -> Option<usize> {
6071        let mut other_facet = self.facet_right_facet(facet_id)?;
6072        let mut guard = 0;
6073        while self.facet_is_pseudohinge(other_facet) {
6074            other_facet = self.facet_right_facet(other_facet)?;
6075            guard += 1;
6076            if guard > self.facets.len() {
6077                return None;
6078            }
6079        }
6080        Some(other_facet)
6081    }
6082
6083    fn facet_is_source(&self, facet_id: usize) -> bool {
6084        let facet = &self.facets[facet_id - 1];
6085        !facet.head_facets.is_empty() && facet.tail_facets.is_empty()
6086    }
6087
6088    fn facet_is_sink(&self, facet_id: usize) -> bool {
6089        let facet = &self.facets[facet_id - 1];
6090        !facet.tail_facets.is_empty() && facet.head_facets.is_empty()
6091    }
6092
6093    fn facet_link_to(&mut self, tail_facet: usize, head_facet: usize) {
6094        self.facets[tail_facet - 1].head_facets.push(head_facet);
6095        self.facets[head_facet - 1].tail_facets.push(tail_facet);
6096    }
6097
6098    fn facets_are_linked(&self, facet1: usize, facet2: usize) -> bool {
6099        self.facets[facet1 - 1].head_facets.contains(&facet2)
6100            || self.facets[facet2 - 1].head_facets.contains(&facet1)
6101    }
6102
6103    fn facet_link(&mut self, facet1: usize, facet2: usize) {
6104        if self.facet_is_sink(facet1) {
6105            self.facet_link_to(facet1, facet2);
6106        } else {
6107            self.facet_link_to(facet2, facet1);
6108        }
6109    }
6110
6111    fn facet_unlink(&mut self, facet1: usize, facet2: usize) {
6112        if let Some(pos) = self.facets[facet1 - 1]
6113            .head_facets
6114            .iter()
6115            .position(|id| *id == facet2)
6116        {
6117            self.facets[facet1 - 1].head_facets.remove(pos);
6118            if let Some(pos) = self.facets[facet2 - 1]
6119                .tail_facets
6120                .iter()
6121                .position(|id| *id == facet1)
6122            {
6123                self.facets[facet2 - 1].tail_facets.remove(pos);
6124            }
6125            return;
6126        }
6127
6128        if let Some(pos) = self.facets[facet1 - 1]
6129            .tail_facets
6130            .iter()
6131            .position(|id| *id == facet2)
6132        {
6133            self.facets[facet1 - 1].tail_facets.remove(pos);
6134            if let Some(pos) = self.facets[facet2 - 1]
6135                .head_facets
6136                .iter()
6137                .position(|id| *id == facet1)
6138            {
6139                self.facets[facet2 - 1].head_facets.remove(pos);
6140            }
6141        }
6142    }
6143
6144    fn vertex_is_axial(&self, vertex_id: usize) -> bool {
6145        self.vertices[vertex_id - 1]
6146            .creases
6147            .iter()
6148            .any(|crease_id| self.creases[*crease_id - 1].kind == CREASE_AXIAL)
6149    }
6150
6151    fn vertex_degree(&self, vertex_id: usize, crease_list: &[usize]) -> usize {
6152        self.vertices[vertex_id - 1]
6153            .creases
6154            .iter()
6155            .filter(|crease_id| crease_list.contains(crease_id))
6156            .count()
6157    }
6158
6159    fn vertex_num_hinge_creases(&self, vertex_id: usize) -> usize {
6160        self.vertices[vertex_id - 1]
6161            .creases
6162            .iter()
6163            .filter(|crease_id| self.crease_is_hinge(**crease_id))
6164            .count()
6165    }
6166
6167    fn vertex_hinge_crease(&self, vertex_id: usize) -> Option<usize> {
6168        self.vertices[vertex_id - 1]
6169            .creases
6170            .iter()
6171            .copied()
6172            .find(|crease_id| self.crease_is_hinge(*crease_id))
6173    }
6174
6175    fn vertex_hinge_creases(&self, vertex_id: usize) -> Vec<usize> {
6176        self.vertices[vertex_id - 1]
6177            .creases
6178            .iter()
6179            .copied()
6180            .filter(|crease_id| self.crease_is_hinge(*crease_id))
6181            .take(2)
6182            .collect()
6183    }
6184
6185    fn vertex_swap_links(&mut self, vertex_id: usize) {
6186        if !self.vertex_is_axial(vertex_id) || self.vertex_num_hinge_creases(vertex_id) < 2 {
6187            return;
6188        }
6189        let hinge_creases = self.vertex_hinge_creases(vertex_id);
6190        if hinge_creases.len() < 2 {
6191            return;
6192        }
6193        let crease1 = hinge_creases[0];
6194        let crease2 = hinge_creases[1];
6195        let (Some(facet_a), Some(facet_b), Some(facet_c), Some(facet_d)) = (
6196            self.crease_left_facet(crease1),
6197            self.crease_right_facet(crease1),
6198            self.crease_right_facet(crease2),
6199            self.crease_left_facet(crease2),
6200        ) else {
6201            return;
6202        };
6203        self.facet_unlink(facet_a, facet_b);
6204        self.facet_unlink(facet_c, facet_d);
6205        self.facet_link_to(facet_a, facet_c);
6206        self.facet_link_to(facet_d, facet_b);
6207    }
6208
6209    fn leaf_path_id_between(&self, node1: usize, node2: usize) -> Option<usize> {
6210        self.paths
6211            .iter()
6212            .find(|path| {
6213                path.is_leaf
6214                    && matches!(
6215                        path.nodes.first().copied().zip(path.nodes.last().copied()),
6216                        Some((a, b)) if (a == node1 && b == node2) || (a == node2 && b == node1)
6217                    )
6218            })
6219            .map(|path| path.index)
6220    }
6221
6222    fn rebuild_conditioned_flags(&mut self) {
6223        let mut conditioned_nodes = vec![false; self.nodes.len()];
6224        let mut conditioned_edges = vec![false; self.edges.len()];
6225        let mut conditioned_paths = vec![false; self.paths.len()];
6226
6227        for condition in &self.conditions {
6228            condition.kind.collect_conditioned_parts(
6229                self,
6230                &mut conditioned_nodes,
6231                &mut conditioned_edges,
6232                &mut conditioned_paths,
6233            );
6234        }
6235
6236        for (node, conditioned) in self.nodes.iter_mut().zip(conditioned_nodes) {
6237            node.is_conditioned = conditioned;
6238        }
6239        for (edge, conditioned) in self.edges.iter_mut().zip(conditioned_edges) {
6240            edge.is_conditioned = conditioned;
6241        }
6242        for (path, conditioned) in self.paths.iter_mut().zip(conditioned_paths) {
6243            path.is_conditioned = conditioned;
6244        }
6245    }
6246}
6247
6248struct Reader<'a> {
6249    bytes: &'a [u8],
6250    pos: usize,
6251}
6252
6253impl<'a> Reader<'a> {
6254    fn new(input: &'a str) -> Self {
6255        Self {
6256            bytes: input.as_bytes(),
6257            pos: 0,
6258        }
6259    }
6260
6261    fn expect_tag(&mut self, expected: &'static str) -> Result<()> {
6262        let tag = self.read_token("tag")?;
6263        if tag != expected {
6264            return Err(self.err(format!("expected tag {expected:?}, found {tag:?}")));
6265        }
6266        Ok(())
6267    }
6268
6269    fn read_token(&mut self, label: &'static str) -> Result<String> {
6270        self.skip_leading_ws();
6271        if self.pos >= self.bytes.len() {
6272            return Err(self.err(format!("expected {label}, found end of input")));
6273        }
6274        let start = self.pos;
6275        while self.pos < self.bytes.len() && !self.bytes[self.pos].is_ascii_whitespace() {
6276            self.pos += 1;
6277        }
6278        if start == self.pos {
6279            return Err(self.err(format!("expected {label}")));
6280        }
6281        let token = std::str::from_utf8(&self.bytes[start..self.pos])
6282            .map_err(|_| self.err(format!("{label} was not UTF-8")))?
6283            .to_string();
6284        self.consume_trailing_space();
6285        Ok(token)
6286    }
6287
6288    fn read_line_field(&mut self, label: &'static str) -> Result<String> {
6289        if self.pos >= self.bytes.len() {
6290            return Err(self.err(format!("expected {label}, found end of input")));
6291        }
6292        let mut out = Vec::new();
6293        while self.pos < self.bytes.len() {
6294            let b = self.bytes[self.pos];
6295            self.pos += 1;
6296            match b {
6297                b'\r' => {
6298                    if self.pos < self.bytes.len() && self.bytes[self.pos] == b'\n' {
6299                        self.pos += 1;
6300                    }
6301                    break;
6302                }
6303                b'\n' => break,
6304                b'\\' => {
6305                    let Some(next) = self.bytes.get(self.pos).copied() else {
6306                        return Err(self.err("dangling escape in C string".to_string()));
6307                    };
6308                    self.pos += 1;
6309                    match next {
6310                        b'n' => out.push(b'\n'),
6311                        b'r' => out.push(b'\r'),
6312                        b'\\' => out.push(b'\\'),
6313                        other => {
6314                            return Err(self.err(format!(
6315                                "bad escape sequence \\{} in {label}",
6316                                other as char
6317                            )));
6318                        }
6319                    }
6320                }
6321                other => out.push(other),
6322            }
6323        }
6324        String::from_utf8(out).map_err(|_| self.err(format!("{label} was not UTF-8")))
6325    }
6326
6327    fn read_raw_line(&mut self, label: &'static str) -> Result<String> {
6328        if self.pos >= self.bytes.len() {
6329            return Err(self.err(format!("expected {label}, found end of input")));
6330        }
6331        let start = self.pos;
6332        while self.pos < self.bytes.len() {
6333            match self.bytes[self.pos] {
6334                b'\r' => {
6335                    let end = self.pos;
6336                    self.pos += 1;
6337                    if self.pos < self.bytes.len() && self.bytes[self.pos] == b'\n' {
6338                        self.pos += 1;
6339                    }
6340                    return std::str::from_utf8(&self.bytes[start..end])
6341                        .map(|s| s.to_string())
6342                        .map_err(|_| self.err(format!("{label} was not UTF-8")));
6343                }
6344                b'\n' => {
6345                    let end = self.pos;
6346                    self.pos += 1;
6347                    return std::str::from_utf8(&self.bytes[start..end])
6348                        .map(|s| s.to_string())
6349                        .map_err(|_| self.err(format!("{label} was not UTF-8")));
6350                }
6351                _ => self.pos += 1,
6352            }
6353        }
6354        std::str::from_utf8(&self.bytes[start..self.pos])
6355            .map(|s| s.to_string())
6356            .map_err(|_| self.err(format!("{label} was not UTF-8")))
6357    }
6358
6359    fn read_usize(&mut self, label: &'static str) -> Result<usize> {
6360        let offset = self.pos;
6361        let token = self.read_token(label)?;
6362        token.parse::<usize>().map_err(|_| TreeError::Parse {
6363            offset,
6364            message: format!("expected unsigned integer for {label}, found {token:?}"),
6365        })
6366    }
6367
6368    fn read_i32(&mut self, label: &'static str) -> Result<i32> {
6369        let offset = self.pos;
6370        let token = self.read_token(label)?;
6371        token.parse::<i32>().map_err(|_| TreeError::Parse {
6372            offset,
6373            message: format!("expected integer for {label}, found {token:?}"),
6374        })
6375    }
6376
6377    fn read_f64(&mut self, label: &'static str) -> Result<TmFloat> {
6378        let offset = self.pos;
6379        let token = self.read_token(label)?;
6380        if token.starts_with("NAN") {
6381            return Ok(0.0);
6382        }
6383        token.parse::<TmFloat>().map_err(|_| TreeError::Parse {
6384            offset,
6385            message: format!("expected float for {label}, found {token:?}"),
6386        })
6387    }
6388
6389    fn read_bool(&mut self, label: &'static str) -> Result<bool> {
6390        Ok(self.read_token(label)? == "true")
6391    }
6392
6393    fn read_point(&mut self, label: &'static str) -> Result<Point> {
6394        Ok(Point {
6395            x: self.read_f64(label)?,
6396            y: self.read_f64(label)?,
6397        })
6398    }
6399
6400    fn read_index_array(&mut self, label: &'static str) -> Result<Vec<usize>> {
6401        let n = self.read_usize(label)?;
6402        let mut values = Vec::with_capacity(n);
6403        for _ in 0..n {
6404            values.push(self.read_usize(label)?);
6405        }
6406        Ok(values)
6407    }
6408
6409    fn read_optional_index_in_range(
6410        &mut self,
6411        label: &'static str,
6412        max: usize,
6413    ) -> Result<Option<usize>> {
6414        match self.read_usize(label)? {
6415            0 => Ok(None),
6416            n if n <= max => Ok(Some(n)),
6417            _ => Ok(None),
6418        }
6419    }
6420
6421    fn read_point_array(&mut self, label: &'static str) -> Result<Vec<Point>> {
6422        let n = self.read_usize(label)?;
6423        let mut points = Vec::with_capacity(n);
6424        for _ in 0..n {
6425            points.push(self.read_point(label)?);
6426        }
6427        Ok(points)
6428    }
6429
6430    fn read_node_v3(&mut self, conditions: &mut Vec<Condition>) -> Result<Node> {
6431        self.expect_tag("node")?;
6432        let index = self.read_usize("node index")?;
6433        let label = self.read_line_field("node label")?;
6434        let loc = self.read_point("node location")?;
6435
6436        let node_is_symmetric = self.read_bool("node symmetric flag")?;
6437        if node_is_symmetric {
6438            push_condition(conditions, ConditionKind::NodeSymmetric { node: index });
6439        }
6440
6441        let node_is_paired = self.read_bool("node paired flag")?;
6442        let paired_node = self.read_usize("paired node")?;
6443        if node_is_paired && index > paired_node {
6444            push_condition(
6445                conditions,
6446                ConditionKind::NodesPaired {
6447                    node1: index,
6448                    node2: paired_node,
6449                },
6450            );
6451        }
6452
6453        let x_fixed = self.read_bool("node x fixed flag")?;
6454        let y_fixed = self.read_bool("node y fixed flag")?;
6455        let x_fix_value = self.read_f64("node x fixed value")?;
6456        let y_fix_value = self.read_f64("node y fixed value")?;
6457        if x_fixed || y_fixed {
6458            push_condition(
6459                conditions,
6460                ConditionKind::NodeFixed {
6461                    node: index,
6462                    x_fixed,
6463                    y_fixed,
6464                    x_fix_value: if x_fixed { x_fix_value } else { 0.0 },
6465                    y_fix_value: if y_fixed { y_fix_value } else { 0.0 },
6466                },
6467            );
6468        }
6469
6470        let node_stick_to_edge = self.read_bool("node stick-to-edge flag")?;
6471        if node_stick_to_edge {
6472            push_condition(conditions, ConditionKind::NodeOnEdge { node: index });
6473        }
6474
6475        let node_is_collinear = self.read_bool("node collinear flag")?;
6476        let collinear_node1 = self.read_usize("collinear node 1")?;
6477        let collinear_node2 = self.read_usize("collinear node 2")?;
6478        if node_is_collinear && index > collinear_node1 && index > collinear_node2 {
6479            push_condition(
6480                conditions,
6481                ConditionKind::NodesCollinear {
6482                    node1: index,
6483                    node2: collinear_node1,
6484                    node3: collinear_node2,
6485                },
6486            );
6487        }
6488
6489        Ok(Node {
6490            index,
6491            label,
6492            loc,
6493            depth: DEPTH_NOT_SET,
6494            elevation: 0.0,
6495            is_leaf: self.read_bool("node leaf flag")?,
6496            is_sub: false,
6497            is_border: self.read_bool("node border flag")?,
6498            is_pinned: self.read_bool("node pinned flag")?,
6499            is_polygon: self.read_bool("node polygon flag")?,
6500            is_junction: false,
6501            is_conditioned: false,
6502            owned_vertices: Vec::new(),
6503            edges: self.read_index_array("node edges")?,
6504            leaf_paths: self.read_index_array("node leaf paths")?,
6505            owner: OwnerRef::Tree,
6506        })
6507    }
6508
6509    fn read_node_v4(&mut self) -> Result<Node> {
6510        self.expect_tag("node")?;
6511        Ok(Node {
6512            index: self.read_usize("node index")?,
6513            label: self.read_line_field("node label")?,
6514            loc: self.read_point("node location")?,
6515            depth: DEPTH_NOT_SET,
6516            elevation: 0.0,
6517            is_leaf: self.read_bool("node leaf flag")?,
6518            is_sub: self.read_bool("node sub flag")?,
6519            is_border: self.read_bool("node border flag")?,
6520            is_pinned: self.read_bool("node pinned flag")?,
6521            is_polygon: self.read_bool("node polygon flag")?,
6522            is_junction: false,
6523            is_conditioned: self.read_bool("node conditioned flag")?,
6524            owned_vertices: self.read_index_array("node owned vertices")?,
6525            edges: self.read_index_array("node edges")?,
6526            leaf_paths: self.read_index_array("node leaf paths")?,
6527            owner: self.read_node_owner()?,
6528        })
6529    }
6530
6531    fn read_node_v5(&mut self) -> Result<Node> {
6532        self.expect_tag("node")?;
6533        Ok(Node {
6534            index: self.read_usize("node index")?,
6535            label: self.read_line_field("node label")?,
6536            loc: self.read_point("node location")?,
6537            depth: self.read_f64("node depth")?,
6538            elevation: self.read_f64("node elevation")?,
6539            is_leaf: self.read_bool("node leaf flag")?,
6540            is_sub: self.read_bool("node sub flag")?,
6541            is_border: self.read_bool("node border flag")?,
6542            is_pinned: self.read_bool("node pinned flag")?,
6543            is_polygon: self.read_bool("node polygon flag")?,
6544            is_junction: self.read_bool("node junction flag")?,
6545            is_conditioned: self.read_bool("node conditioned flag")?,
6546            edges: self.read_index_array("node edges")?,
6547            leaf_paths: self.read_index_array("node leaf paths")?,
6548            owned_vertices: self.read_index_array("node owned vertices")?,
6549            owner: self.read_node_owner()?,
6550        })
6551    }
6552
6553    fn read_edge_v3(&mut self) -> Result<Edge> {
6554        self.expect_tag("edge")?;
6555        Ok(Edge {
6556            index: self.read_usize("edge index")?,
6557            label: self.read_line_field("edge label")?,
6558            length: self.read_f64("edge length")?,
6559            strain: 0.0,
6560            stiffness: 0.0,
6561            is_pinned: self.read_bool("edge pinned flag")?,
6562            is_conditioned: false,
6563            nodes: self.read_index_array("edge nodes")?,
6564        })
6565    }
6566
6567    fn read_edge(&mut self, repair_zero_stiffness: bool) -> Result<Edge> {
6568        self.expect_tag("edge")?;
6569        let index = self.read_usize("edge index")?;
6570        let label = self.read_line_field("edge label")?;
6571        let length = self.read_f64("edge length")?;
6572        let strain = self.read_f64("edge strain")?;
6573        let mut stiffness = self.read_f64("edge stiffness")?;
6574        if repair_zero_stiffness && stiffness == 0.0 {
6575            stiffness = 1.0;
6576        }
6577        let edge = Edge {
6578            index,
6579            label,
6580            length,
6581            strain,
6582            stiffness,
6583            is_pinned: self.read_bool("edge pinned flag")?,
6584            is_conditioned: self.read_bool("edge conditioned flag")?,
6585            nodes: self.read_index_array("edge nodes")?,
6586        };
6587        Ok(edge)
6588    }
6589
6590    fn read_path_v3(&mut self, conditions: &mut Vec<Condition>) -> Result<Path> {
6591        self.expect_tag("path")?;
6592        let index = self.read_usize("path index")?;
6593        let min_tree_length = self.read_f64("path min tree length")?;
6594        let path_fixed_length = self.read_bool("path fixed length flag")?;
6595        let path_fixed_length_value = self.read_f64("path fixed length value")?;
6596        let path_fixed_angle = self.read_bool("path fixed angle flag")?;
6597        let _path_fixed_angle_value = self.read_f64("path fixed angle value")?;
6598        let is_leaf = self.read_bool("path leaf flag")?;
6599        let is_active = self.read_bool("path active flag")?;
6600        let is_border = self.read_bool("path border flag")?;
6601        let is_polygon = self.read_bool("path polygon flag")?;
6602        let _legacy_fwd_poly = self.read_usize("path fwd poly")?;
6603        let _legacy_bkd_poly = self.read_usize("path bkd poly")?;
6604        let nodes = self.read_index_array("path nodes")?;
6605        let edges = self.read_index_array("path edges")?;
6606
6607        if let Some((node1, node2)) = nodes.first().copied().zip(nodes.last().copied()) {
6608            if path_fixed_length && min_tree_length == path_fixed_length_value {
6609                push_condition(conditions, ConditionKind::PathActive { node1, node2 });
6610            }
6611            if path_fixed_angle {
6612                // TreeMaker 5.0.1 reads the serialized angle value, but assigns
6613                // the fixed-angle boolean to the condition angle in v3 import.
6614                push_condition(
6615                    conditions,
6616                    ConditionKind::PathAngleFixed {
6617                        node1,
6618                        node2,
6619                        angle: 1.0,
6620                    },
6621                );
6622            }
6623        }
6624
6625        Ok(Path {
6626            index,
6627            min_tree_length,
6628            min_paper_length: 0.0,
6629            act_tree_length: 0.0,
6630            act_paper_length: 0.0,
6631            is_leaf,
6632            is_sub: false,
6633            is_feasible: false,
6634            is_active,
6635            is_border,
6636            is_polygon,
6637            is_conditioned: false,
6638            fwd_poly: None,
6639            bkd_poly: None,
6640            nodes,
6641            edges,
6642            outset_path: None,
6643            front_reduction: 0.0,
6644            back_reduction: 0.0,
6645            min_depth: DEPTH_NOT_SET,
6646            min_depth_dist: DEPTH_NOT_SET,
6647            owned_vertices: Vec::new(),
6648            owned_creases: Vec::new(),
6649            owner: OwnerRef::Tree,
6650        })
6651    }
6652
6653    fn read_path_v4(&mut self, poly_count: usize) -> Result<Path> {
6654        self.expect_tag("path")?;
6655        Ok(Path {
6656            index: self.read_usize("path index")?,
6657            min_tree_length: self.read_f64("path min tree length")?,
6658            min_paper_length: self.read_f64("path min paper length")?,
6659            act_tree_length: 0.0,
6660            act_paper_length: 0.0,
6661            is_leaf: self.read_bool("path leaf flag")?,
6662            is_sub: self.read_bool("path sub flag")?,
6663            is_feasible: false,
6664            is_active: self.read_bool("path active flag")?,
6665            is_border: self.read_bool("path border flag")?,
6666            is_polygon: self.read_bool("path polygon flag")?,
6667            is_conditioned: self.read_bool("path conditioned flag")?,
6668            owned_vertices: self.read_index_array("path owned vertices")?,
6669            fwd_poly: self.read_optional_index_in_range("path fwd poly", poly_count)?,
6670            bkd_poly: self.read_optional_index_in_range("path bkd poly", poly_count)?,
6671            nodes: self.read_index_array("path nodes")?,
6672            edges: self.read_index_array("path edges")?,
6673            owner: self.read_path_owner()?,
6674            outset_path: None,
6675            front_reduction: 0.0,
6676            back_reduction: 0.0,
6677            min_depth: DEPTH_NOT_SET,
6678            min_depth_dist: DEPTH_NOT_SET,
6679            owned_creases: Vec::new(),
6680        })
6681    }
6682
6683    fn read_path_v5(&mut self, poly_count: usize, path_count: usize) -> Result<Path> {
6684        self.expect_tag("path")?;
6685        Ok(Path {
6686            index: self.read_usize("path index")?,
6687            min_tree_length: self.read_f64("path min tree length")?,
6688            min_paper_length: self.read_f64("path min paper length")?,
6689            act_tree_length: self.read_f64("path actual tree length")?,
6690            act_paper_length: self.read_f64("path actual paper length")?,
6691            is_leaf: self.read_bool("path leaf flag")?,
6692            is_sub: self.read_bool("path sub flag")?,
6693            is_feasible: self.read_bool("path feasible flag")?,
6694            is_active: self.read_bool("path active flag")?,
6695            is_border: self.read_bool("path border flag")?,
6696            is_polygon: self.read_bool("path polygon flag")?,
6697            is_conditioned: self.read_bool("path conditioned flag")?,
6698            fwd_poly: self.read_optional_index_in_range("path fwd poly", poly_count)?,
6699            bkd_poly: self.read_optional_index_in_range("path bkd poly", poly_count)?,
6700            nodes: self.read_index_array("path nodes")?,
6701            edges: self.read_index_array("path edges")?,
6702            outset_path: self.read_optional_index_in_range("path outset", path_count)?,
6703            front_reduction: self.read_f64("path front reduction")?,
6704            back_reduction: self.read_f64("path back reduction")?,
6705            min_depth: self.read_f64("path min depth")?,
6706            min_depth_dist: self.read_f64("path min depth distance")?,
6707            owned_vertices: self.read_index_array("path owned vertices")?,
6708            owned_creases: self.read_index_array("path owned creases")?,
6709            owner: self.read_path_owner()?,
6710        })
6711    }
6712
6713    fn read_poly_v4(&mut self, path_count: usize) -> Result<Poly> {
6714        self.expect_tag("poly")?;
6715        let index = self.read_usize("poly index")?;
6716        let centroid = self.read_point("poly centroid")?;
6717        let node_locs = self.read_point_array("poly node locations")?;
6718        let is_sub_poly = self.read_bool("poly sub flag")?;
6719        let owned_nodes = self.read_index_array("poly owned nodes")?;
6720        let owned_paths = self.read_index_array("poly owned paths")?;
6721        let owned_polys = self.read_index_array("poly owned polys")?;
6722        let owned_creases = self.read_index_array("poly owned creases")?;
6723        let ring_nodes = self.read_index_array("poly ring nodes")?;
6724        let ring_paths = self.read_index_array("poly ring paths")?;
6725        let cross_paths = self.read_index_array("poly cross paths")?;
6726        let inset_nodes = self.read_index_array("poly inset nodes")?;
6727        let spoke_paths = self.read_index_array("poly spoke paths")?;
6728        let ridge_path = self.read_optional_index_in_range("poly ridge path", path_count)?;
6729        let owner = self.read_poly_owner()?;
6730        Ok(Poly {
6731            index,
6732            centroid,
6733            is_sub_poly,
6734            ring_nodes,
6735            ring_paths,
6736            cross_paths,
6737            inset_nodes,
6738            spoke_paths,
6739            ridge_path,
6740            node_locs,
6741            local_root_vertices: Vec::new(),
6742            local_root_creases: Vec::new(),
6743            owned_nodes,
6744            owned_paths,
6745            owned_polys,
6746            owned_creases,
6747            owned_facets: Vec::new(),
6748            owner,
6749        })
6750    }
6751
6752    fn read_poly_v5(&mut self, path_count: usize) -> Result<Poly> {
6753        self.expect_tag("poly")?;
6754        let index = self.read_usize("poly index")?;
6755        let centroid = self.read_point("poly centroid")?;
6756        let is_sub_poly = self.read_bool("poly sub flag")?;
6757        Ok(Poly {
6758            index,
6759            centroid,
6760            is_sub_poly,
6761            ring_nodes: self.read_index_array("poly ring nodes")?,
6762            ring_paths: self.read_index_array("poly ring paths")?,
6763            cross_paths: self.read_index_array("poly cross paths")?,
6764            inset_nodes: self.read_index_array("poly inset nodes")?,
6765            spoke_paths: self.read_index_array("poly spoke paths")?,
6766            ridge_path: self.read_optional_index_in_range("poly ridge path", path_count)?,
6767            node_locs: self.read_point_array("poly node locations")?,
6768            local_root_vertices: self.read_index_array("poly local root vertices")?,
6769            local_root_creases: self.read_index_array("poly local root creases")?,
6770            owned_nodes: self.read_index_array("poly owned nodes")?,
6771            owned_paths: self.read_index_array("poly owned paths")?,
6772            owned_polys: self.read_index_array("poly owned polys")?,
6773            owned_creases: self.read_index_array("poly owned creases")?,
6774            owned_facets: self.read_index_array("poly owned facets")?,
6775            owner: self.read_poly_owner()?,
6776        })
6777    }
6778
6779    fn read_vertex_v4(&mut self, index: usize) -> Result<Vertex> {
6780        self.expect_tag("vrtx")?;
6781        Ok(Vertex {
6782            index,
6783            loc: self.read_point("vertex location")?,
6784            elevation: 0.0,
6785            is_border: false,
6786            tree_node: None,
6787            left_pseudohinge_mate: None,
6788            right_pseudohinge_mate: None,
6789            creases: self.read_index_array("vertex creases")?,
6790            depth: DEPTH_NOT_SET,
6791            discrete_depth: 0,
6792            cc_flag: 0,
6793            st_flag: 0,
6794            owner: self.read_vertex_owner()?,
6795        })
6796    }
6797
6798    fn read_vertex_v5(&mut self, node_count: usize, vertex_count: usize) -> Result<Vertex> {
6799        self.expect_tag("vrtx")?;
6800        let index = self.read_usize("vertex index")?;
6801        Ok(Vertex {
6802            index,
6803            loc: self.read_point("vertex location")?,
6804            elevation: self.read_f64("vertex elevation")?,
6805            is_border: self.read_bool("vertex border flag")?,
6806            tree_node: self.read_optional_index_in_range("vertex tree node", node_count)?,
6807            left_pseudohinge_mate: self
6808                .read_optional_index_in_range("vertex left pseudohinge mate", vertex_count)?,
6809            right_pseudohinge_mate: self
6810                .read_optional_index_in_range("vertex right pseudohinge mate", vertex_count)?,
6811            creases: self.read_index_array("vertex creases")?,
6812            depth: self.read_f64("vertex depth")?,
6813            discrete_depth: self.read_usize("vertex discrete depth")?,
6814            cc_flag: self.read_i32("vertex cc flag")?,
6815            st_flag: self.read_i32("vertex st flag")?,
6816            owner: self.read_vertex_owner()?,
6817        })
6818    }
6819
6820    fn read_crease_v4(&mut self, index: usize) -> Result<Crease> {
6821        self.expect_tag("crse")?;
6822        Ok(Crease {
6823            index,
6824            kind: self.read_i32("crease kind")?,
6825            vertices: self.read_index_array("crease vertices")?,
6826            fwd_facet: None,
6827            bkd_facet: None,
6828            fold: 0,
6829            cc_flag: 0,
6830            st_flag: 0,
6831            owner: self.read_crease_owner()?,
6832        })
6833    }
6834
6835    fn read_crease_v5(&mut self, facet_count: usize) -> Result<Crease> {
6836        self.expect_tag("crse")?;
6837        let index = self.read_usize("crease index")?;
6838        Ok(Crease {
6839            index,
6840            kind: self.read_i32("crease kind")?,
6841            vertices: self.read_index_array("crease vertices")?,
6842            fwd_facet: self.read_optional_index_in_range("crease fwd facet", facet_count)?,
6843            bkd_facet: self.read_optional_index_in_range("crease bkd facet", facet_count)?,
6844            fold: self.read_i32("crease fold")?,
6845            cc_flag: self.read_i32("crease cc flag")?,
6846            st_flag: self.read_i32("crease st flag")?,
6847            owner: self.read_crease_owner()?,
6848        })
6849    }
6850
6851    fn read_facet_v5(&mut self, edge_count: usize) -> Result<Facet> {
6852        self.expect_tag("fact")?;
6853        let index = self.read_usize("facet index")?;
6854        Ok(Facet {
6855            index,
6856            centroid: self.read_point("facet centroid")?,
6857            is_well_formed: self.read_bool("facet well-formed flag")?,
6858            vertices: self.read_index_array("facet vertices")?,
6859            creases: self.read_index_array("facet creases")?,
6860            corridor_edge: self.read_optional_index_in_range("facet corridor edge", edge_count)?,
6861            head_facets: self.read_index_array("facet head facets")?,
6862            tail_facets: self.read_index_array("facet tail facets")?,
6863            order: self.read_usize("facet order")?,
6864            color: self.read_i32("facet color")?,
6865            owner: self.read_facet_owner()?,
6866        })
6867    }
6868
6869    fn read_condition_v4(&mut self, index: usize) -> Result<Condition> {
6870        let tag = self.read_token("condition tag")?;
6871        if tag.len() > 4 {
6872            return Err(self.err(format!("bad condition tag {tag:?}")));
6873        }
6874        let n = self.read_usize("condition line count")?;
6875        validate_condition_rest_len(&tag, n)?;
6876        let mut raw_lines = Vec::with_capacity(n);
6877        for _ in 0..n {
6878            raw_lines.push(self.read_raw_line("condition field")?);
6879        }
6880        let kind = ConditionKind::from_stream(&tag, &raw_lines)?;
6881        Ok(Condition {
6882            index,
6883            is_feasible: true,
6884            kind,
6885        })
6886    }
6887
6888    fn read_condition_v5(&mut self) -> Result<Condition> {
6889        let tag = self.read_token("condition tag")?;
6890        if tag.len() > 4 {
6891            return Err(self.err(format!("bad condition tag {tag:?}")));
6892        }
6893        let index = self.read_usize("condition index")?;
6894        let is_feasible = self.read_bool("condition feasibility")?;
6895        let n = self.read_usize("condition line count")?;
6896        validate_condition_rest_len(&tag, n)?;
6897        let mut raw_lines = Vec::with_capacity(n);
6898        for _ in 0..n {
6899            raw_lines.push(self.read_raw_line("condition field")?);
6900        }
6901        let kind = ConditionKind::from_stream(&tag, &raw_lines)?;
6902        Ok(Condition {
6903            index,
6904            is_feasible,
6905            kind,
6906        })
6907    }
6908
6909    fn read_node_owner(&mut self) -> Result<OwnerRef> {
6910        if self.read_usize("node owner is poly")? == 1 {
6911            Ok(OwnerRef::Poly(self.read_usize("node owner poly")?))
6912        } else {
6913            Ok(OwnerRef::Tree)
6914        }
6915    }
6916
6917    fn read_path_owner(&mut self) -> Result<OwnerRef> {
6918        if self.read_usize("path owner is poly")? == 1 {
6919            Ok(OwnerRef::Poly(self.read_usize("path owner poly")?))
6920        } else {
6921            Ok(OwnerRef::Tree)
6922        }
6923    }
6924
6925    fn read_poly_owner(&mut self) -> Result<OwnerRef> {
6926        if self.read_usize("poly owner is poly")? == 1 {
6927            Ok(OwnerRef::Poly(self.read_usize("poly owner poly")?))
6928        } else {
6929            Ok(OwnerRef::Tree)
6930        }
6931    }
6932
6933    fn read_vertex_owner(&mut self) -> Result<OwnerRef> {
6934        if self.read_usize("vertex owner is node")? == 1 {
6935            Ok(OwnerRef::Node(self.read_usize("vertex owner node")?))
6936        } else {
6937            Ok(OwnerRef::Path(self.read_usize("vertex owner path")?))
6938        }
6939    }
6940
6941    fn read_crease_owner(&mut self) -> Result<OwnerRef> {
6942        if self.read_usize("crease owner is poly")? == 1 {
6943            Ok(OwnerRef::Poly(self.read_usize("crease owner poly")?))
6944        } else {
6945            Ok(OwnerRef::Path(self.read_usize("crease owner path")?))
6946        }
6947    }
6948
6949    fn read_facet_owner(&mut self) -> Result<OwnerRef> {
6950        Ok(OwnerRef::Poly(self.read_usize("facet owner poly")?))
6951    }
6952
6953    fn skip_leading_ws(&mut self) {
6954        while self.pos < self.bytes.len() && self.bytes[self.pos].is_ascii_whitespace() {
6955            self.pos += 1;
6956        }
6957    }
6958
6959    fn consume_trailing_space(&mut self) {
6960        while self.pos < self.bytes.len()
6961            && (self.bytes[self.pos] == b' ' || self.bytes[self.pos] == b'\t')
6962        {
6963            self.pos += 1;
6964        }
6965        if self.pos < self.bytes.len() {
6966            match self.bytes[self.pos] {
6967                b'\n' => self.pos += 1,
6968                b'\r' => {
6969                    self.pos += 1;
6970                    if self.pos < self.bytes.len() && self.bytes[self.pos] == b'\n' {
6971                        self.pos += 1;
6972                    }
6973                }
6974                _ => {}
6975            }
6976        }
6977    }
6978
6979    fn err(&self, message: String) -> TreeError {
6980        TreeError::Parse {
6981            offset: self.pos,
6982            message,
6983        }
6984    }
6985}
6986
6987struct Writer {
6988    precision: usize,
6989    eol: &'static str,
6990    out: String,
6991}
6992
6993impl Writer {
6994    fn new(precision: usize, eol: &'static str) -> Self {
6995        Self {
6996            precision,
6997            eol,
6998            out: String::new(),
6999        }
7000    }
7001
7002    fn finish(self) -> String {
7003        self.out
7004    }
7005
7006    fn line(&mut self, value: impl AsRef<str>) {
7007        self.out.push_str(value.as_ref());
7008        self.out.push_str(self.eol);
7009    }
7010
7011    fn s(&mut self, value: &str) {
7012        self.line(value);
7013    }
7014
7015    fn cstr(&mut self, value: &str) {
7016        let mut escaped = String::new();
7017        for ch in value.chars() {
7018            match ch {
7019                '\n' => escaped.push_str("\\n"),
7020                '\r' => escaped.push_str("\\r"),
7021                '\\' => escaped.push_str("\\\\"),
7022                _ => escaped.push(ch),
7023            }
7024        }
7025        self.line(escaped);
7026    }
7027
7028    fn u(&mut self, value: usize) {
7029        self.line(value.to_string());
7030    }
7031
7032    fn i(&mut self, value: i32) {
7033        self.line(value.to_string());
7034    }
7035
7036    fn f(&mut self, value: TmFloat) {
7037        self.line(fmt_float(value, self.precision));
7038    }
7039
7040    fn b(&mut self, value: bool) {
7041        self.line(if value { "true" } else { "false" });
7042    }
7043
7044    fn point(&mut self, value: Point) {
7045        self.f(value.x);
7046        self.f(value.y);
7047    }
7048
7049    fn array(&mut self, values: &[usize]) {
7050        self.u(values.len());
7051        for value in values {
7052            self.u(*value);
7053        }
7054    }
7055
7056    fn point_array(&mut self, values: &[Point]) {
7057        self.u(values.len());
7058        for value in values {
7059            self.point(*value);
7060        }
7061    }
7062
7063    fn owner_node_or_tree(&mut self, owner: &OwnerRef) {
7064        match owner {
7065            OwnerRef::Poly(id) => {
7066                self.u(1);
7067                self.u(*id);
7068            }
7069            _ => self.u(0),
7070        }
7071    }
7072
7073    fn owner_vertex(&mut self, owner: &OwnerRef) {
7074        match owner {
7075            OwnerRef::Node(id) => {
7076                self.u(1);
7077                self.u(*id);
7078            }
7079            OwnerRef::Path(id) => {
7080                self.u(0);
7081                self.u(*id);
7082            }
7083            _ => {
7084                self.u(0);
7085                self.u(0);
7086            }
7087        }
7088    }
7089
7090    fn owner_crease(&mut self, owner: &OwnerRef) {
7091        match owner {
7092            OwnerRef::Poly(id) => {
7093                self.u(1);
7094                self.u(*id);
7095            }
7096            OwnerRef::Path(id) => {
7097                self.u(0);
7098                self.u(*id);
7099            }
7100            _ => {
7101                self.u(0);
7102                self.u(0);
7103            }
7104        }
7105    }
7106
7107    fn owner_facet(&mut self, owner: &OwnerRef) {
7108        match owner {
7109            OwnerRef::Poly(id) => self.u(*id),
7110            _ => self.u(0),
7111        }
7112    }
7113
7114    fn node_v4(&mut self, node: &Node) {
7115        self.s("node");
7116        self.u(node.index);
7117        self.cstr(&node.label);
7118        self.point(node.loc);
7119        self.b(node.is_leaf);
7120        self.b(node.is_sub);
7121        self.b(node.is_border);
7122        self.b(node.is_pinned);
7123        self.b(node.is_polygon);
7124        self.b(node.is_conditioned);
7125        self.array(&node.owned_vertices);
7126        self.array(&node.edges);
7127        self.array(&node.leaf_paths);
7128        self.owner_node_or_tree(&node.owner);
7129    }
7130
7131    fn node_v5(&mut self, node: &Node) {
7132        self.s("node");
7133        self.u(node.index);
7134        self.cstr(&node.label);
7135        self.point(node.loc);
7136        self.f(node.depth);
7137        self.f(node.elevation);
7138        self.b(node.is_leaf);
7139        self.b(node.is_sub);
7140        self.b(node.is_border);
7141        self.b(node.is_pinned);
7142        self.b(node.is_polygon);
7143        self.b(node.is_junction);
7144        self.b(node.is_conditioned);
7145        self.array(&node.edges);
7146        self.array(&node.leaf_paths);
7147        self.array(&node.owned_vertices);
7148        self.owner_node_or_tree(&node.owner);
7149    }
7150
7151    fn edge(&mut self, edge: &Edge) {
7152        self.s("edge");
7153        self.u(edge.index);
7154        self.cstr(&edge.label);
7155        self.f(edge.length);
7156        self.f(edge.strain);
7157        self.f(edge.stiffness);
7158        self.b(edge.is_pinned);
7159        self.b(edge.is_conditioned);
7160        self.array(&edge.nodes);
7161    }
7162
7163    fn path_v4(&mut self, path: &Path) {
7164        self.s("path");
7165        self.u(path.index);
7166        self.f(path.min_tree_length);
7167        self.f(path.min_paper_length);
7168        self.b(path.is_leaf);
7169        self.b(path.is_sub);
7170        self.b(path.is_active);
7171        self.b(path.is_border);
7172        self.b(path.is_polygon);
7173        self.b(path.is_conditioned);
7174        self.array(&path.owned_vertices);
7175        self.u(path.fwd_poly.unwrap_or(0));
7176        self.u(path.bkd_poly.unwrap_or(0));
7177        self.array(&path.nodes);
7178        self.array(&path.edges);
7179        self.owner_node_or_tree(&path.owner);
7180    }
7181
7182    fn path_v5(&mut self, path: &Path) {
7183        self.s("path");
7184        self.u(path.index);
7185        self.f(path.min_tree_length);
7186        self.f(path.min_paper_length);
7187        self.f(path.act_tree_length);
7188        self.f(path.act_paper_length);
7189        self.b(path.is_leaf);
7190        self.b(path.is_sub);
7191        self.b(path.is_feasible);
7192        self.b(path.is_active);
7193        self.b(path.is_border);
7194        self.b(path.is_polygon);
7195        self.b(path.is_conditioned);
7196        self.u(path.fwd_poly.unwrap_or(0));
7197        self.u(path.bkd_poly.unwrap_or(0));
7198        self.array(&path.nodes);
7199        self.array(&path.edges);
7200        self.u(path.outset_path.unwrap_or(0));
7201        self.f(path.front_reduction);
7202        self.f(path.back_reduction);
7203        self.f(path.min_depth);
7204        self.f(path.min_depth_dist);
7205        self.array(&path.owned_vertices);
7206        self.array(&path.owned_creases);
7207        self.owner_node_or_tree(&path.owner);
7208    }
7209
7210    fn poly_v5(&mut self, poly: &Poly) {
7211        self.s("poly");
7212        self.u(poly.index);
7213        self.point(poly.centroid);
7214        self.b(poly.is_sub_poly);
7215        self.array(&poly.ring_nodes);
7216        self.array(&poly.ring_paths);
7217        self.array(&poly.cross_paths);
7218        self.array(&poly.inset_nodes);
7219        self.array(&poly.spoke_paths);
7220        self.u(poly.ridge_path.unwrap_or(0));
7221        self.point_array(&poly.node_locs);
7222        self.array(&poly.local_root_vertices);
7223        self.array(&poly.local_root_creases);
7224        self.array(&poly.owned_nodes);
7225        self.array(&poly.owned_paths);
7226        self.array(&poly.owned_polys);
7227        self.array(&poly.owned_creases);
7228        self.array(&poly.owned_facets);
7229        self.owner_node_or_tree(&poly.owner);
7230    }
7231
7232    fn vertex_v5(&mut self, vertex: &Vertex) {
7233        self.s("vrtx");
7234        self.u(vertex.index);
7235        self.point(vertex.loc);
7236        self.f(vertex.elevation);
7237        self.b(vertex.is_border);
7238        self.u(vertex.tree_node.unwrap_or(0));
7239        self.u(vertex.left_pseudohinge_mate.unwrap_or(0));
7240        self.u(vertex.right_pseudohinge_mate.unwrap_or(0));
7241        self.array(&vertex.creases);
7242        self.f(vertex.depth);
7243        self.u(vertex.discrete_depth);
7244        self.i(vertex.cc_flag);
7245        self.i(vertex.st_flag);
7246        self.owner_vertex(&vertex.owner);
7247    }
7248
7249    fn crease_v5(&mut self, crease: &Crease) {
7250        self.s("crse");
7251        self.u(crease.index);
7252        self.i(crease.kind);
7253        self.array(&crease.vertices);
7254        self.u(crease.fwd_facet.unwrap_or(0));
7255        self.u(crease.bkd_facet.unwrap_or(0));
7256        self.i(crease.fold);
7257        self.i(crease.cc_flag);
7258        self.i(crease.st_flag);
7259        self.owner_crease(&crease.owner);
7260    }
7261
7262    fn facet_v5(&mut self, facet: &Facet) {
7263        self.s("fact");
7264        self.u(facet.index);
7265        self.point(facet.centroid);
7266        self.b(facet.is_well_formed);
7267        self.array(&facet.vertices);
7268        self.array(&facet.creases);
7269        self.u(facet.corridor_edge.unwrap_or(0));
7270        self.array(&facet.head_facets);
7271        self.array(&facet.tail_facets);
7272        self.u(facet.order);
7273        self.i(facet.color);
7274        self.owner_facet(&facet.owner);
7275    }
7276
7277    fn condition_v4(&mut self, condition: &Condition) {
7278        self.s(condition.kind.tag());
7279        let lines = condition.kind.stream_lines(self.precision);
7280        self.u(lines.len());
7281        for line in &lines {
7282            self.line(line);
7283        }
7284    }
7285
7286    fn condition_v5(&mut self, condition: &Condition) {
7287        self.s(condition.kind.tag());
7288        self.u(condition.index);
7289        self.b(condition.is_feasible);
7290        let lines = condition.kind.stream_lines(self.precision);
7291        self.u(lines.len());
7292        for line in &lines {
7293            self.line(line);
7294        }
7295    }
7296}
7297
7298struct ScaleObjective;
7299
7300impl nlco::DifferentiableFn for ScaleObjective {
7301    fn func(&self, x: &[f64]) -> f64 {
7302        -x[0]
7303    }
7304
7305    fn grad(&self, _x: &[f64], grad: &mut [f64]) {
7306        grad.fill(0.0);
7307        grad[0] = -1.0;
7308    }
7309}
7310
7311struct StrainObjective {
7312    edge_offset: usize,
7313    stiffness: Vec<TmFloat>,
7314}
7315
7316impl nlco::DifferentiableFn for StrainObjective {
7317    fn func(&self, x: &[f64]) -> f64 {
7318        let mut value = 0.0;
7319        for (i, x_i) in x.iter().enumerate().skip(self.edge_offset) {
7320            value += self.stiffness[i - self.edge_offset] * x_i.powi(2);
7321        }
7322        value
7323    }
7324
7325    fn grad(&self, x: &[f64], grad: &mut [f64]) {
7326        grad.fill(0.0);
7327        for (i, x_i) in x.iter().enumerate().skip(self.edge_offset) {
7328            grad[i] = 2.0 * self.stiffness[i - self.edge_offset] * *x_i;
7329        }
7330    }
7331}
7332
7333impl ConditionKind {
7334    fn from_stream(tag: &str, lines: &[String]) -> Result<Self> {
7335        validate_condition_rest_len(tag, lines.len())?;
7336        let kind = match tag {
7337            "CNxn" => Self::NodeCombo {
7338                node: parse_condition_usize(&lines[0], "condition node")?,
7339                to_symmetry_line: parse_condition_bool(&lines[1]),
7340                to_paper_edge: parse_condition_bool(&lines[2]),
7341                to_paper_corner: parse_condition_bool(&lines[3]),
7342                x_fixed: parse_condition_bool(&lines[4]),
7343                x_fix_value: parse_condition_f64(&lines[5], "condition x fixed value")?,
7344                y_fixed: parse_condition_bool(&lines[6]),
7345                y_fix_value: parse_condition_f64(&lines[7], "condition y fixed value")?,
7346            },
7347            "CNfn" => Self::NodeFixed {
7348                node: parse_condition_usize(&lines[0], "condition node")?,
7349                x_fixed: parse_condition_bool(&lines[1]),
7350                y_fixed: parse_condition_bool(&lines[2]),
7351                x_fix_value: parse_condition_f64(&lines[3], "condition x fixed value")?,
7352                y_fix_value: parse_condition_f64(&lines[4], "condition y fixed value")?,
7353            },
7354            "CNkn" => Self::NodeOnCorner {
7355                node: parse_condition_usize(&lines[0], "condition node")?,
7356            },
7357            "CNen" => Self::NodeOnEdge {
7358                node: parse_condition_usize(&lines[0], "condition node")?,
7359            },
7360            "CNsn" => Self::NodeSymmetric {
7361                node: parse_condition_usize(&lines[0], "condition node")?,
7362            },
7363            "CNpn" => Self::NodesPaired {
7364                node1: parse_condition_usize(&lines[0], "condition node 1")?,
7365                node2: parse_condition_usize(&lines[1], "condition node 2")?,
7366            },
7367            "CNcn" => Self::NodesCollinear {
7368                node1: parse_condition_usize(&lines[0], "condition node 1")?,
7369                node2: parse_condition_usize(&lines[1], "condition node 2")?,
7370                node3: parse_condition_usize(&lines[2], "condition node 3")?,
7371            },
7372            "CNfe" => Self::EdgeLengthFixed {
7373                edge: parse_condition_usize(&lines[0], "condition edge")?,
7374            },
7375            "CNes" => Self::EdgesSameStrain {
7376                edge1: parse_condition_usize(&lines[0], "condition edge 1")?,
7377                edge2: parse_condition_usize(&lines[1], "condition edge 2")?,
7378            },
7379            "CNap" => Self::PathActive {
7380                node1: parse_condition_usize(&lines[0], "condition node 1")?,
7381                node2: parse_condition_usize(&lines[1], "condition node 2")?,
7382            },
7383            "CNfp" => Self::PathAngleFixed {
7384                node1: parse_condition_usize(&lines[0], "condition node 1")?,
7385                node2: parse_condition_usize(&lines[1], "condition node 2")?,
7386                angle: parse_condition_f64(&lines[2], "condition angle")?,
7387            },
7388            "CNqp" => Self::PathAngleQuant {
7389                node1: parse_condition_usize(&lines[0], "condition node 1")?,
7390                node2: parse_condition_usize(&lines[1], "condition node 2")?,
7391                quant: parse_condition_usize(&lines[2], "condition quantization")?,
7392                quant_offset: parse_condition_f64(&lines[3], "condition quantization offset")?,
7393            },
7394            "CNxp" => Self::PathCombo {
7395                node1: parse_condition_usize(&lines[0], "condition node 1")?,
7396                node2: parse_condition_usize(&lines[1], "condition node 2")?,
7397                is_angle_fixed: parse_condition_bool(&lines[2]),
7398                angle: parse_condition_f64(&lines[3], "condition angle")?,
7399                is_angle_quant: parse_condition_bool(&lines[4]),
7400                quant: parse_condition_usize(&lines[5], "condition quantization")?,
7401                quant_offset: parse_condition_f64(&lines[6], "condition quantization offset")?,
7402            },
7403            _ => unreachable!("validate_condition_rest_len accepted an unrecognized tag"),
7404        };
7405        Ok(kind)
7406    }
7407
7408    fn tag(&self) -> &'static str {
7409        match self {
7410            Self::NodeCombo { .. } => "CNxn",
7411            Self::NodeFixed { .. } => "CNfn",
7412            Self::NodeOnCorner { .. } => "CNkn",
7413            Self::NodeOnEdge { .. } => "CNen",
7414            Self::NodeSymmetric { .. } => "CNsn",
7415            Self::NodesPaired { .. } => "CNpn",
7416            Self::NodesCollinear { .. } => "CNcn",
7417            Self::EdgeLengthFixed { .. } => "CNfe",
7418            Self::EdgesSameStrain { .. } => "CNes",
7419            Self::PathCombo { .. } => "CNxp",
7420            Self::PathActive { .. } => "CNap",
7421            Self::PathAngleFixed { .. } => "CNfp",
7422            Self::PathAngleQuant { .. } => "CNqp",
7423        }
7424    }
7425
7426    fn stream_lines(&self, precision: usize) -> Vec<String> {
7427        match self {
7428            Self::NodeCombo {
7429                node,
7430                to_symmetry_line,
7431                to_paper_edge,
7432                to_paper_corner,
7433                x_fixed,
7434                x_fix_value,
7435                y_fixed,
7436                y_fix_value,
7437            } => vec![
7438                node.to_string(),
7439                bool_stream(*to_symmetry_line),
7440                bool_stream(*to_paper_edge),
7441                bool_stream(*to_paper_corner),
7442                bool_stream(*x_fixed),
7443                fmt_float(*x_fix_value, precision),
7444                bool_stream(*y_fixed),
7445                fmt_float(*y_fix_value, precision),
7446            ],
7447            Self::NodeFixed {
7448                node,
7449                x_fixed,
7450                y_fixed,
7451                x_fix_value,
7452                y_fix_value,
7453            } => vec![
7454                node.to_string(),
7455                bool_stream(*x_fixed),
7456                bool_stream(*y_fixed),
7457                fmt_float(*x_fix_value, precision),
7458                fmt_float(*y_fix_value, precision),
7459            ],
7460            Self::NodeOnCorner { node }
7461            | Self::NodeOnEdge { node }
7462            | Self::NodeSymmetric { node } => vec![node.to_string()],
7463            Self::NodesPaired { node1, node2 } | Self::PathActive { node1, node2 } => {
7464                vec![node1.to_string(), node2.to_string()]
7465            }
7466            Self::NodesCollinear {
7467                node1,
7468                node2,
7469                node3,
7470            } => vec![node1.to_string(), node2.to_string(), node3.to_string()],
7471            Self::EdgeLengthFixed { edge } => vec![edge.to_string()],
7472            Self::EdgesSameStrain { edge1, edge2 } => {
7473                vec![edge1.to_string(), edge2.to_string()]
7474            }
7475            Self::PathCombo {
7476                node1,
7477                node2,
7478                is_angle_fixed,
7479                angle,
7480                is_angle_quant,
7481                quant,
7482                quant_offset,
7483            } => vec![
7484                node1.to_string(),
7485                node2.to_string(),
7486                bool_stream(*is_angle_fixed),
7487                fmt_float(*angle, precision),
7488                bool_stream(*is_angle_quant),
7489                quant.to_string(),
7490                fmt_float(*quant_offset, precision),
7491            ],
7492            Self::PathAngleFixed {
7493                node1,
7494                node2,
7495                angle,
7496            } => vec![
7497                node1.to_string(),
7498                node2.to_string(),
7499                fmt_float(*angle, precision),
7500            ],
7501            Self::PathAngleQuant {
7502                node1,
7503                node2,
7504                quant,
7505                quant_offset,
7506            } => vec![
7507                node1.to_string(),
7508                node2.to_string(),
7509                quant.to_string(),
7510                fmt_float(*quant_offset, precision),
7511            ],
7512        }
7513    }
7514
7515    fn validate_refs(&self, tree: &Tree) -> Result<()> {
7516        match self {
7517            Self::NodeCombo { node, .. }
7518            | Self::NodeFixed { node, .. }
7519            | Self::NodeOnCorner { node }
7520            | Self::NodeOnEdge { node }
7521            | Self::NodeSymmetric { node } => tree.check_ref("node", *node, tree.nodes.len()),
7522            Self::NodesPaired { node1, node2 }
7523            | Self::PathActive { node1, node2 }
7524            | Self::PathAngleFixed { node1, node2, .. }
7525            | Self::PathAngleQuant { node1, node2, .. }
7526            | Self::PathCombo { node1, node2, .. } => {
7527                tree.check_ref("node", *node1, tree.nodes.len())?;
7528                tree.check_ref("node", *node2, tree.nodes.len())
7529            }
7530            Self::NodesCollinear {
7531                node1,
7532                node2,
7533                node3,
7534            } => {
7535                tree.check_ref("node", *node1, tree.nodes.len())?;
7536                tree.check_ref("node", *node2, tree.nodes.len())?;
7537                tree.check_ref("node", *node3, tree.nodes.len())
7538            }
7539            Self::EdgeLengthFixed { edge } => tree.check_ref("edge", *edge, tree.edges.len()),
7540            Self::EdgesSameStrain { edge1, edge2 } => {
7541                tree.check_ref("edge", *edge1, tree.edges.len())?;
7542                tree.check_ref("edge", *edge2, tree.edges.len())
7543            }
7544        }
7545    }
7546
7547    fn calc_feasibility(&self, tree: &Tree) -> bool {
7548        match self {
7549            Self::NodeCombo {
7550                node,
7551                to_symmetry_line,
7552                to_paper_edge,
7553                to_paper_corner,
7554                x_fixed,
7555                x_fix_value,
7556                y_fixed,
7557                y_fix_value,
7558            } => {
7559                let Some(loc) = node_loc(tree, *node) else {
7560                    return false;
7561                };
7562                if tree.has_symmetry
7563                    && *to_symmetry_line
7564                    && !is_tiny(stick_to_line(loc, tree.sym_loc, tree.sym_angle))
7565                {
7566                    return false;
7567                }
7568                if *to_paper_edge
7569                    && !is_tiny(stick_to_edge(loc, tree.paper_width, tree.paper_height))
7570                {
7571                    return false;
7572                }
7573                if *to_paper_corner {
7574                    if !is_tiny(corner_coord(loc.x, tree.paper_width)) {
7575                        return false;
7576                    }
7577                    // TreeMaker 5.0.1's CNxn feasibility check uses paper width
7578                    // for the y corner coordinate; preserve that behavior.
7579                    if !is_tiny(corner_coord(loc.y, tree.paper_width)) {
7580                        return false;
7581                    }
7582                }
7583                if *x_fixed && !is_tiny(*x_fix_value - loc.x) {
7584                    return false;
7585                }
7586                !*y_fixed || is_tiny(*y_fix_value - loc.y)
7587            }
7588            Self::NodeFixed {
7589                node,
7590                x_fixed,
7591                y_fixed,
7592                x_fix_value,
7593                y_fix_value,
7594            } => {
7595                let Some(loc) = node_loc(tree, *node) else {
7596                    return false;
7597                };
7598                if *x_fixed && !is_tiny(*x_fix_value - loc.x) {
7599                    return false;
7600                }
7601                !*y_fixed || is_tiny(*y_fix_value - loc.y)
7602            }
7603            Self::NodeOnCorner { node } => node_loc(tree, *node).is_some_and(|loc| {
7604                is_tiny(corner_coord(loc.x, tree.paper_width))
7605                    && is_tiny(corner_coord(loc.y, tree.paper_height))
7606            }),
7607            Self::NodeOnEdge { node } => node_loc(tree, *node).is_some_and(|loc| {
7608                is_tiny(stick_to_edge(loc, tree.paper_width, tree.paper_height))
7609            }),
7610            Self::NodeSymmetric { node } => {
7611                tree.has_symmetry
7612                    && node_loc(tree, *node).is_some_and(|loc| {
7613                        is_tiny(stick_to_line(loc, tree.sym_loc, tree.sym_angle))
7614                    })
7615            }
7616            Self::NodesPaired { node1, node2 } => {
7617                if !tree.has_symmetry {
7618                    return false;
7619                }
7620                let (Some(a), Some(b)) = (node_loc(tree, *node1), node_loc(tree, *node2)) else {
7621                    return false;
7622                };
7623                is_tiny(pair_fn_1a(a, b, tree.sym_angle))
7624                    && is_tiny(pair_fn_1b(a, b, tree.sym_loc, tree.sym_angle))
7625            }
7626            Self::NodesCollinear {
7627                node1,
7628                node2,
7629                node3,
7630            } => {
7631                let (Some(a), Some(b), Some(c)) = (
7632                    node_loc(tree, *node1),
7633                    node_loc(tree, *node2),
7634                    node_loc(tree, *node3),
7635                ) else {
7636                    return false;
7637                };
7638                is_tiny(collinear_fn_1(a, b, c))
7639            }
7640            Self::EdgeLengthFixed { edge } => {
7641                edge_ref(tree, *edge).is_some_and(|edge| is_tiny(edge.strain))
7642            }
7643            Self::EdgesSameStrain { edge1, edge2 } => {
7644                let (Some(a), Some(b)) = (edge_ref(tree, *edge1), edge_ref(tree, *edge2)) else {
7645                    return false;
7646                };
7647                is_tiny(a.strain - b.strain)
7648            }
7649            Self::PathActive { node1, node2 } => tree
7650                .find_leaf_path_between(*node1, *node2)
7651                .is_some_and(|path| path.is_active),
7652            Self::PathAngleFixed {
7653                node1,
7654                node2,
7655                angle,
7656            } => path_active_with_nodes(tree, *node1, *node2)
7657                .is_some_and(|(a, b)| is_tiny(path_angle_fn_1(a, b, *angle))),
7658            Self::PathAngleQuant {
7659                node1,
7660                node2,
7661                quant,
7662                quant_offset,
7663            } => path_active_with_nodes(tree, *node1, *node2)
7664                .is_some_and(|(a, b)| is_tiny(quantize_angle_fn_1(a, b, *quant, *quant_offset))),
7665            Self::PathCombo {
7666                node1,
7667                node2,
7668                is_angle_fixed,
7669                angle,
7670                is_angle_quant,
7671                quant,
7672                quant_offset,
7673            } => {
7674                let Some((a, b)) = path_active_with_nodes(tree, *node1, *node2) else {
7675                    return false;
7676                };
7677                if *is_angle_fixed && !is_tiny(path_angle_fn_1(a, b, *angle)) {
7678                    return false;
7679                }
7680                !*is_angle_quant || is_tiny(quantize_angle_fn_1(a, b, *quant, *quant_offset))
7681            }
7682        }
7683    }
7684
7685    fn collect_conditioned_parts(
7686        &self,
7687        tree: &Tree,
7688        nodes: &mut [bool],
7689        edges: &mut [bool],
7690        paths: &mut [bool],
7691    ) {
7692        match *self {
7693            Self::NodeCombo { node, .. }
7694            | Self::NodeFixed { node, .. }
7695            | Self::NodeOnCorner { node }
7696            | Self::NodeOnEdge { node }
7697            | Self::NodeSymmetric { node } => mark_1_based(nodes, node),
7698            Self::NodesPaired { node1, node2 } => {
7699                mark_1_based(nodes, node1);
7700                mark_1_based(nodes, node2);
7701            }
7702            Self::PathActive { node1, node2 }
7703            | Self::PathAngleFixed { node1, node2, .. }
7704            | Self::PathAngleQuant { node1, node2, .. }
7705            | Self::PathCombo { node1, node2, .. } => {
7706                mark_1_based(nodes, node1);
7707                mark_1_based(nodes, node2);
7708                if let Some(path) = tree.find_leaf_path_between(node1, node2) {
7709                    mark_1_based(paths, path.index);
7710                }
7711            }
7712            Self::NodesCollinear {
7713                node1,
7714                node2,
7715                node3,
7716            } => {
7717                mark_1_based(nodes, node1);
7718                mark_1_based(nodes, node2);
7719                mark_1_based(nodes, node3);
7720            }
7721            Self::EdgeLengthFixed { edge } => mark_1_based(edges, edge),
7722            Self::EdgesSameStrain { edge1, edge2 } => {
7723                mark_1_based(edges, edge1);
7724                mark_1_based(edges, edge2);
7725            }
7726        }
7727    }
7728
7729    fn remap_nodes(&mut self, map: &[Option<usize>]) {
7730        match self {
7731            Self::NodeCombo { node, .. }
7732            | Self::NodeFixed { node, .. }
7733            | Self::NodeOnCorner { node }
7734            | Self::NodeOnEdge { node }
7735            | Self::NodeSymmetric { node } => remap_value(node, map),
7736            Self::NodesPaired { node1, node2 }
7737            | Self::PathActive { node1, node2 }
7738            | Self::PathAngleFixed { node1, node2, .. }
7739            | Self::PathAngleQuant { node1, node2, .. }
7740            | Self::PathCombo { node1, node2, .. } => {
7741                remap_value(node1, map);
7742                remap_value(node2, map);
7743            }
7744            Self::NodesCollinear {
7745                node1,
7746                node2,
7747                node3,
7748            } => {
7749                remap_value(node1, map);
7750                remap_value(node2, map);
7751                remap_value(node3, map);
7752            }
7753            Self::EdgeLengthFixed { .. } | Self::EdgesSameStrain { .. } => {}
7754        }
7755    }
7756}
7757
7758fn push_condition(conditions: &mut Vec<Condition>, kind: ConditionKind) {
7759    conditions.push(Condition {
7760        index: conditions.len() + 1,
7761        is_feasible: true,
7762        kind,
7763    });
7764}
7765
7766fn kill_v4_crease_pattern_refs(nodes: &mut [Node], paths: &mut [Path]) {
7767    for node in nodes {
7768        node.owned_vertices.clear();
7769        if matches!(node.owner, OwnerRef::Poly(_)) {
7770            node.owner = OwnerRef::Tree;
7771        }
7772    }
7773    for path in paths {
7774        path.owned_vertices.clear();
7775        path.owned_creases.clear();
7776        path.fwd_poly = None;
7777        path.bkd_poly = None;
7778        path.outset_path = None;
7779        if matches!(path.owner, OwnerRef::Poly(_)) {
7780            path.owner = OwnerRef::Tree;
7781        }
7782    }
7783}
7784
7785#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7786enum PartKind {
7787    Node,
7788    Path,
7789    Poly,
7790}
7791
7792fn ids_to_flags(ids: &[usize], len: usize) -> Vec<bool> {
7793    let mut flags = vec![false; len + 1];
7794    for id in ids {
7795        if *id > 0 && *id <= len {
7796            flags[*id] = true;
7797        }
7798    }
7799    flags
7800}
7801
7802fn keep_map(len: usize, doomed: &[usize]) -> Vec<Option<usize>> {
7803    let doomed = ids_to_flags(doomed, len);
7804    let mut map = vec![None; len + 1];
7805    let mut next = 1;
7806    for old in 1..=len {
7807        if !doomed[old] {
7808            map[old] = Some(next);
7809            next += 1;
7810        }
7811    }
7812    map
7813}
7814
7815fn remap_value(value: &mut usize, map: &[Option<usize>]) {
7816    if let Some(Some(mapped)) = map.get(*value) {
7817        *value = *mapped;
7818    }
7819}
7820
7821fn remap_option(value: &mut Option<usize>, map: &[Option<usize>]) {
7822    *value = value.and_then(|id| map.get(id).copied().flatten());
7823}
7824
7825fn remap_vec(values: &mut Vec<usize>, map: &[Option<usize>]) {
7826    let mapped = values
7827        .iter()
7828        .filter_map(|id| map.get(*id).copied().flatten())
7829        .collect();
7830    *values = mapped;
7831}
7832
7833fn remap_owner(owner: &mut OwnerRef, kind: PartKind, map: &[Option<usize>]) {
7834    match owner {
7835        OwnerRef::Node(id) if kind == PartKind::Node => {
7836            if let Some(mapped) = map.get(*id).copied().flatten() {
7837                *id = mapped;
7838            } else {
7839                *owner = OwnerRef::Tree;
7840            }
7841        }
7842        OwnerRef::Path(id) if kind == PartKind::Path => {
7843            if let Some(mapped) = map.get(*id).copied().flatten() {
7844                *id = mapped;
7845            } else {
7846                *owner = OwnerRef::Tree;
7847            }
7848        }
7849        OwnerRef::Poly(id) if kind == PartKind::Poly => {
7850            if let Some(mapped) = map.get(*id).copied().flatten() {
7851                *id = mapped;
7852            } else {
7853                *owner = OwnerRef::Tree;
7854            }
7855        }
7856        _ => {}
7857    }
7858}
7859
7860fn mark_1_based(flags: &mut [bool], index: usize) {
7861    if let Some(flag) = index.checked_sub(1).and_then(|pos| flags.get_mut(pos)) {
7862        *flag = true;
7863    }
7864}
7865
7866fn bool_stream(value: bool) -> String {
7867    (if value { "true" } else { "false" }).to_string()
7868}
7869
7870fn parse_condition_usize(value: &str, label: &'static str) -> Result<usize> {
7871    value.parse::<usize>().map_err(|_| TreeError::Parse {
7872        offset: 0,
7873        message: format!("expected unsigned integer for {label}, found {value:?}"),
7874    })
7875}
7876
7877fn parse_condition_f64(value: &str, label: &'static str) -> Result<TmFloat> {
7878    if value.starts_with("NAN") {
7879        return Ok(0.0);
7880    }
7881    value.parse::<TmFloat>().map_err(|_| TreeError::Parse {
7882        offset: 0,
7883        message: format!("expected float for {label}, found {value:?}"),
7884    })
7885}
7886
7887fn parse_condition_bool(value: &str) -> bool {
7888    value == "true"
7889}
7890
7891fn is_tiny(value: TmFloat) -> bool {
7892    value.abs() < DIST_TOL
7893}
7894
7895fn point_sub(a: Point, b: Point) -> Point {
7896    Point {
7897        x: a.x - b.x,
7898        y: a.y - b.y,
7899    }
7900}
7901
7902fn point_add(a: Point, b: Point) -> Point {
7903    Point {
7904        x: a.x + b.x,
7905        y: a.y + b.y,
7906    }
7907}
7908
7909fn point_mul(a: Point, scale: TmFloat) -> Point {
7910    Point {
7911        x: a.x * scale,
7912        y: a.y * scale,
7913    }
7914}
7915
7916fn point_div(a: Point, scale: TmFloat) -> Point {
7917    Point {
7918        x: a.x / scale,
7919        y: a.y / scale,
7920    }
7921}
7922
7923fn rotate_ccw90(p: Point) -> Point {
7924    Point { x: -p.y, y: p.x }
7925}
7926
7927fn inner(a: Point, b: Point) -> TmFloat {
7928    a.x * b.x + a.y * b.y
7929}
7930
7931fn mag2(p: Point) -> TmFloat {
7932    p.x.powi(2) + p.y.powi(2)
7933}
7934
7935fn mag(p: Point) -> TmFloat {
7936    mag2(p).sqrt()
7937}
7938
7939fn normalize(p: Point) -> Point {
7940    point_div(p, mag(p))
7941}
7942
7943fn orientation_2d(p1: Point, p2: Point, p3: Point) -> TmFloat {
7944    let p13 = point_sub(p1, p3);
7945    let p23 = point_sub(p2, p3);
7946    p13.x * p23.y - p13.y * p23.x
7947}
7948
7949fn are_cw(p1: Point, p2: Point, p3: Point) -> bool {
7950    orientation_2d(p1, p2, p3) < 0.0
7951}
7952
7953fn are_ccw(p1: Point, p2: Point, p3: Point) -> bool {
7954    orientation_2d(p1, p2, p3) > 0.0
7955}
7956
7957fn are_parallel(p: Point, q: Point) -> bool {
7958    inner(p, rotate_ccw90(q)) == 0.0
7959}
7960
7961fn line_intersection_params(
7962    p: Point,
7963    rp: Point,
7964    q: Point,
7965    rq: Point,
7966) -> Option<(TmFloat, TmFloat)> {
7967    let eps = TmFloat::EPSILON.sqrt();
7968    let rrpq = inner(rotate_ccw90(rp), rq);
7969    if rrpq.abs() < eps {
7970        return None;
7971    }
7972    let rqp = rotate_ccw90(point_sub(q, p));
7973    Some((inner(rqp, rq) / rrpq, inner(rqp, rp) / rrpq))
7974}
7975
7976fn line_intersection_point_exact(p: Point, rp: Point, q: Point, rq: Point) -> Option<Point> {
7977    let rrpq = inner(rotate_ccw90(rp), rq);
7978    if rrpq == 0.0 {
7979        return None;
7980    }
7981    let tp = inner(rotate_ccw90(point_sub(q, p)), rq) / rrpq;
7982    Some(point_add(p, point_mul(rp, tp)))
7983}
7984
7985fn incenter(p1: Point, p2: Point, p3: Point) -> Point {
7986    let l12 = p1.distance(p2);
7987    let l23 = p2.distance(p3);
7988    let l31 = p3.distance(p1);
7989    point_div(
7990        point_add(
7991            point_add(point_mul(p3, l12), point_mul(p1, l23)),
7992            point_mul(p2, l31),
7993        ),
7994        l12 + l23 + l31,
7995    )
7996}
7997
7998fn inradius(p1: Point, p2: Point, p3: Point) -> TmFloat {
7999    let a = p1.distance(p2);
8000    let b = p2.distance(p3);
8001    let c = p3.distance(p1);
8002    0.5 * (((b + c - a) * (c + a - b) * (a + b - c)) / (a + b + c)).sqrt()
8003}
8004
8005fn vertices_same_loc(p1: Point, p2: Point) -> bool {
8006    p1.distance(p2) < VERTEX_TOL
8007}
8008
8009fn project_p_to_q(p1: Point, p2: Point, p: Point, q1: Point, q2: Point) -> Option<Point> {
8010    let rq = point_sub(q2, q1);
8011    let dq = mag(rq);
8012    let up = normalize(point_sub(p2, p1));
8013    let denom = inner(up, rq);
8014    if denom == 0.0 {
8015        return None;
8016    }
8017    let d = dq * inner(up, point_sub(p, q1)) / denom;
8018    let q = point_add(q1, point_mul(normalize(rq), d));
8019    (d > -0.9 * DIST_TOL && d < dq + 0.9 * DIST_TOL).then_some(q)
8020}
8021
8022fn project_q_to_p(q: Point, p1: Point, p2: Point) -> Option<Point> {
8023    let rp = point_sub(p2, p1);
8024    let up = normalize(rp);
8025    let d = inner(up, point_sub(q, p1));
8026    let dp = inner(up, rp);
8027    let p = point_add(p1, point_mul(up, d));
8028    (d > -0.9 * DIST_TOL && d < dp + 0.9 * DIST_TOL).then_some(p)
8029}
8030
8031fn sortable_ridge_vertex_value(vertex: Point, front: Point, back: Point) -> TmFloat {
8032    let pu = normalize(point_sub(back, front));
8033    let pv = rotate_ccw90(pu);
8034    let dm = point_mul(point_add(back, front), 0.5);
8035    let dp = point_sub(vertex, dm);
8036    let du = inner(dp, pu);
8037    let dv = inner(dp, pv);
8038    du.atan2(dv)
8039}
8040
8041fn push_unique(values: &mut Vec<usize>, value: usize) {
8042    if !values.contains(&value) {
8043        values.push(value);
8044    }
8045}
8046
8047fn angle(p: Point) -> TmFloat {
8048    p.y.atan2(p.x)
8049}
8050
8051fn angle_change(p1: Point, p2: Point, p3: Point) -> TmFloat {
8052    let a = angle(point_sub(p3, p2)) - angle(point_sub(p2, p1));
8053    if a < -PI {
8054        a + TWO_PI
8055    } else if a >= PI {
8056        a - TWO_PI
8057    } else {
8058        a
8059    }
8060}
8061
8062fn node_loc(tree: &Tree, index: usize) -> Option<Point> {
8063    tree.nodes.get(index.checked_sub(1)?).map(|node| node.loc)
8064}
8065
8066fn edge_ref(tree: &Tree, index: usize) -> Option<&Edge> {
8067    tree.edges.get(index.checked_sub(1)?)
8068}
8069
8070fn path_active_with_nodes(tree: &Tree, node1: usize, node2: usize) -> Option<(Point, Point)> {
8071    tree.find_leaf_path_between(node1, node2)
8072        .filter(|path| path.is_active)?;
8073    Some((node_loc(tree, node1)?, node_loc(tree, node2)?))
8074}
8075
8076fn corner_coord(value: TmFloat, width_or_height: TmFloat) -> TmFloat {
8077    value * (value - width_or_height)
8078}
8079
8080fn stick_to_edge(loc: Point, width: TmFloat, height: TmFloat) -> TmFloat {
8081    10.0 * loc.x * (loc.x - width) * loc.y * (loc.y - height)
8082}
8083
8084fn stick_to_line(loc: Point, point: Point, angle: TmFloat) -> TmFloat {
8085    let radians = angle * DEGREES;
8086    (-loc.x + point.x) * radians.sin() + (loc.y - point.y) * radians.cos()
8087}
8088
8089fn pair_fn_1a(a: Point, b: Point, angle: TmFloat) -> TmFloat {
8090    let radians = angle * DEGREES;
8091    (a.x - b.x) * radians.cos() + (a.y - b.y) * radians.sin()
8092}
8093
8094fn pair_fn_1b(a: Point, b: Point, point: Point, angle: TmFloat) -> TmFloat {
8095    let radians = angle * DEGREES;
8096    (-a.x - b.x + 2.0 * point.x) * radians.sin() + (a.y + b.y - 2.0 * point.y) * radians.cos()
8097}
8098
8099fn collinear_fn_1(a: Point, b: Point, c: Point) -> TmFloat {
8100    (b.y - a.y) * (c.x - b.x) - (c.y - b.y) * (b.x - a.x)
8101}
8102
8103fn path_angle_fn_1(a: Point, b: Point, angle: TmFloat) -> TmFloat {
8104    let radians = angle * DEGREES;
8105    (a.x - b.x) * radians.sin() + (b.y - a.y) * radians.cos()
8106}
8107
8108fn quantize_angle_fn_1(a: Point, b: Point, quant: usize, quant_offset: TmFloat) -> TmFloat {
8109    if quant == 0 {
8110        return TmFloat::NAN;
8111    }
8112    let dx = a.x - b.x;
8113    let dy = a.y - b.y;
8114    let r2 = dx * dx + dy * dy;
8115    let f1 = r2.powf(-0.5 * quant as TmFloat);
8116    let offset = quant_offset * DEGREES;
8117    let step = 180.0 * DEGREES / quant as TmFloat;
8118    let mut f2 = 1.0;
8119    for k in 0..quant {
8120        let angle = k as TmFloat * step - offset;
8121        f2 *= dx * angle.sin() - dy * angle.cos();
8122    }
8123    2.0_f64.powi(quant as i32 - 1) * f1 * f2
8124}
8125
8126fn fmt_float(value: TmFloat, precision: usize) -> String {
8127    format!("{value:.precision$}")
8128}
8129
8130fn validate_condition_rest_len(tag: &str, got: usize) -> Result<()> {
8131    let expected = match tag {
8132        "CNxn" => 8,
8133        "CNfn" => 5,
8134        "CNkn" | "CNen" | "CNsn" | "CNfe" => 1,
8135        "CNpn" | "CNes" | "CNap" => 2,
8136        "CNcn" | "CNfp" => 3,
8137        "CNqp" => 4,
8138        "CNxp" => 7,
8139        _ => {
8140            return Err(TreeError::UnsupportedOperation(
8141                "unrecognized condition tags are not preserved until condition parsing is ported",
8142            ));
8143        }
8144    };
8145    if got != expected {
8146        return Err(TreeError::Parse {
8147            offset: 0,
8148            message: format!("condition {tag} expected {expected} rest lines, found {got}"),
8149        });
8150    }
8151    Ok(())
8152}
8153
8154fn opposite_facet_color(color: i32) -> i32 {
8155    if color == FACET_WHITE_UP {
8156        FACET_COLOR_UP
8157    } else {
8158        FACET_WHITE_UP
8159    }
8160}
8161
8162#[cfg(test)]
8163mod tests {
8164    use super::*;
8165
8166    const FIXTURE_1: &str = include_str!("../testdata/tmModelTester_1.tmd5");
8167    const FIXTURE_2: &str = include_str!("../testdata/tmModelTester_2.tmd5");
8168    const FIXTURE_4: &str = include_str!("../testdata/tmModelTester_4.tmd5");
8169    const FIXTURE_5: &str = include_str!("../testdata/tmModelTester_5.tmd5");
8170    const FIXTURE_V3: &str = include_str!("../testdata/minimal_v3.tmd");
8171    const FIXTURE_CP_V4: &str = include_str!("../testdata/minimal_cp_v4.tmd4");
8172    const FIXTURE_CP_V5: &str = include_str!("../testdata/minimal_cp_v5.tmd5");
8173
8174    #[test]
8175    fn parses_v4_fixture_summary() {
8176        let tree = Tree::from_tmd_str(FIXTURE_1).unwrap();
8177        let summary = tree.summary();
8178        assert_eq!(summary.source_version, "4.0");
8179        assert_eq!(summary.nodes, 4);
8180        assert_eq!(summary.edges, 3);
8181        assert_eq!(summary.paths, 6);
8182        assert_eq!(summary.leaf_nodes, 3);
8183        assert_eq!(summary.leaf_paths, 3);
8184        assert!(summary.is_feasible);
8185    }
8186
8187    #[test]
8188    fn parses_larger_fixtures() {
8189        let tree2 = Tree::from_tmd_str(FIXTURE_2).unwrap();
8190        let tree5 = Tree::from_tmd_str(FIXTURE_5).unwrap();
8191        assert!(tree2.summary().nodes > 10);
8192        assert!(tree5.summary().conditions > 0);
8193    }
8194
8195    #[test]
8196    fn parses_v3_and_translates_legacy_conditions() {
8197        let tree = Tree::from_tmd_str(FIXTURE_V3).unwrap();
8198        let summary = tree.summary();
8199        assert_eq!(summary.source_version, "3.0");
8200        assert_eq!(summary.nodes, 2);
8201        assert_eq!(summary.edges, 1);
8202        assert_eq!(summary.paths, 1);
8203        assert_eq!(summary.conditions, 3);
8204        assert_eq!(summary.conditioned_nodes, 2);
8205        assert_eq!(summary.conditioned_paths, 1);
8206        assert_eq!(summary.conditions_by_tag.get("CNfn"), Some(&2));
8207        assert_eq!(summary.conditions_by_tag.get("CNap"), Some(&1));
8208        assert!(summary.is_feasible);
8209    }
8210
8211    #[test]
8212    fn v3_fixed_angle_preserves_501_import_behavior() {
8213        let text = FIXTURE_V3.replacen("false\n0.0000000000\ntrue", "true\n45.0000000000\ntrue", 1);
8214        let tree = Tree::from_tmd_str(&text).unwrap();
8215        let angle = tree.conditions.iter().find_map(|condition| {
8216            if let ConditionKind::PathAngleFixed { angle, .. } = condition.kind {
8217                Some(angle)
8218            } else {
8219                None
8220            }
8221        });
8222        assert_eq!(angle, Some(1.0));
8223    }
8224
8225    #[test]
8226    fn parses_and_round_trips_v5_crease_pattern_payload() {
8227        let tree = Tree::from_tmd_str(FIXTURE_CP_V5).unwrap();
8228        let summary = tree.summary();
8229        assert_eq!(summary.source_version, "5.0");
8230        assert_eq!(summary.polys, 1);
8231        assert_eq!(summary.vertices, 2);
8232        assert_eq!(summary.creases, 1);
8233        assert_eq!(summary.facets, 1);
8234        assert_eq!(tree.polys[0].ring_nodes, vec![1, 2]);
8235        assert_eq!(tree.vertices[0].owner, OwnerRef::Node(1));
8236        assert_eq!(tree.creases[0].owner, OwnerRef::Path(1));
8237        assert_eq!(tree.facets[0].owner, OwnerRef::Poly(1));
8238
8239        let reparsed = Tree::from_tmd_str(&tree.to_tmd5_string()).unwrap();
8240        assert_eq!(reparsed.summary().polys, 1);
8241        assert_eq!(reparsed.summary().vertices, 2);
8242        assert_eq!(reparsed.summary().creases, 1);
8243        assert_eq!(reparsed.summary().facets, 1);
8244    }
8245
8246    #[test]
8247    fn cleanup_after_optimizer_removes_stale_crease_pattern_payload() {
8248        let mut tree = Tree::from_tmd_str(FIXTURE_CP_V5).unwrap();
8249        assert_eq!(tree.summary().polys, 1);
8250
8251        tree.optimize_scale().unwrap();
8252        let summary = tree.summary();
8253        assert_eq!(summary.polys, 0);
8254        assert_eq!(summary.vertices, 0);
8255        assert_eq!(summary.creases, 0);
8256        assert_eq!(summary.facets, 0);
8257        assert!(!tree.is_polygon_valid);
8258        assert!(!tree.is_polygon_filled);
8259        assert!(!tree.is_vertex_depth_valid);
8260        assert!(!tree.is_facet_data_valid);
8261    }
8262
8263    #[test]
8264    fn consumes_and_discards_v4_crease_pattern_payload() {
8265        let tree = Tree::from_tmd_str(FIXTURE_CP_V4).unwrap();
8266        let summary = tree.summary();
8267        assert_eq!(summary.source_version, "4.0");
8268        assert_eq!(summary.nodes, 2);
8269        assert_eq!(summary.edges, 1);
8270        assert_eq!(summary.paths, 1);
8271        assert_eq!(summary.polys, 0);
8272        assert_eq!(summary.vertices, 0);
8273        assert_eq!(summary.creases, 0);
8274        assert_eq!(tree.nodes[0].owned_vertices.len(), 0);
8275        assert_eq!(tree.paths[0].fwd_poly, None);
8276        assert_eq!(tree.paths[0].owned_vertices.len(), 0);
8277    }
8278
8279    #[test]
8280    fn round_trips_through_v5_writer() {
8281        let tree = Tree::from_tmd_str(FIXTURE_1).unwrap();
8282        let serialized = tree.to_tmd5_string();
8283        let reparsed = Tree::from_tmd_str(&serialized).unwrap();
8284        assert_eq!(tree.summary().nodes, reparsed.summary().nodes);
8285        assert_eq!(tree.summary().edges, reparsed.summary().edges);
8286        assert_eq!(tree.summary().paths, reparsed.summary().paths);
8287        assert_eq!(tree.summary().leaf_paths, reparsed.summary().leaf_paths);
8288    }
8289
8290    #[test]
8291    fn scale_optimizer_uses_alm_port() {
8292        let mut tree = Tree::from_tmd_str(FIXTURE_1).unwrap();
8293        let report = tree.optimize_scale().unwrap();
8294        assert!(report.converged);
8295        assert!(tree.is_feasible());
8296        assert!((report.new_scale - 0.517637).abs() < 1.0e-4);
8297    }
8298
8299    #[test]
8300    fn edge_optimizer_uses_alm_port() {
8301        let mut tree = Tree::from_tmd_str(FIXTURE_4).unwrap();
8302        let report = tree.optimize_edges().unwrap();
8303        assert!(report.converged);
8304        assert!(tree.is_feasible());
8305        let max_strain = tree
8306            .edges
8307            .iter()
8308            .map(|edge| edge.strain)
8309            .fold(TmFloat::NEG_INFINITY, TmFloat::max);
8310        assert!((max_strain - 0.573142).abs() < 1.0e-4);
8311    }
8312
8313    #[test]
8314    fn strain_optimizer_uses_alm_port() {
8315        let mut tree = Tree::from_tmd_str(FIXTURE_5).unwrap();
8316        let report = tree.optimize_strain().unwrap();
8317        assert!(report.converged);
8318        assert!(tree.is_feasible());
8319        let weighted = tree
8320            .edges
8321            .iter()
8322            .map(|edge| edge.stiffness * edge.strain.powi(2))
8323            .sum::<TmFloat>()
8324            / tree.edges.len() as TmFloat;
8325        assert!((100.0 * weighted.sqrt() - 3.580266).abs() < 1.0e-4);
8326    }
8327
8328    #[test]
8329    fn builds_polys_and_crease_pattern_payload() {
8330        let mut tree = Tree::from_tmd_str(FIXTURE_1).unwrap();
8331        tree.build_polys_and_crease_pattern().unwrap();
8332        assert!(!tree.vertices.is_empty());
8333        assert!(!tree.creases.is_empty());
8334        assert!(!tree.facets.is_empty());
8335    }
8336
8337    #[test]
8338    fn cp_status_report_identifies_bad_parts() {
8339        let mut tree = Tree::from_tmd_str(FIXTURE_1).unwrap();
8340        tree.build_polys_and_crease_pattern().unwrap();
8341        let report = tree.cp_status_report();
8342        assert_eq!(report.status, CPStatus::PolysMultipleIbps);
8343        assert_eq!(report.bad_polys, vec![1]);
8344        assert!(report.bad_edges.is_empty());
8345        assert!(report.bad_vertices.is_empty());
8346        assert!(report.bad_creases.is_empty());
8347        assert!(report.bad_facets.is_empty());
8348    }
8349}