From dbeda7ffbca90477b51d3b3cf81e706a3e146c2b Mon Sep 17 00:00:00 2001 From: aniket giri Date: Thu, 8 Jan 2026 23:09:03 +0530 Subject: [PATCH] Add Curvature enum for index-based handle references (#1833) This is the first step toward making SegmentDomain use handles from the Point table instead of storing coordinates inline. The Curvature enum describes segment curvature by referencing handle point indices in PointDomain, enabling: - Shared handle points across segments - Attribute-driven transformations - Future extensibility for Arc, NURBS, and other curve types Variants: - Linear: straight line between endpoints - Quadratic: single control point (index reference) - Cubic: two control points (index references) Includes unit tests for all methods. --- .../src/vector/vector_attributes.rs | 163 ++++++++++++++++++ 1 file changed, 163 insertions(+) diff --git a/node-graph/libraries/vector-types/src/vector/vector_attributes.rs b/node-graph/libraries/vector-types/src/vector/vector_attributes.rs index b8cfff2304..4833b00fb7 100644 --- a/node-graph/libraries/vector-types/src/vector/vector_attributes.rs +++ b/node-graph/libraries/vector-types/src/vector/vector_attributes.rs @@ -51,6 +51,99 @@ macro_rules! create_ids { create_ids! { PointId, SegmentId, RegionId, StrokeId, FillId } +/// Describes the curvature type of a segment by referencing handle point indices in [`PointDomain`]. +/// +/// This enum replaces inline coordinate storage with index references, enabling: +/// - Shared handle points across segments +/// - Attribute-driven transformations +/// - Future extensibility for Arc, NURBS, and other curve types +/// +/// See: +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, DynAny, serde::Serialize, serde::Deserialize)] +pub enum Curvature { + /// Linear segment with no curvature (straight line between endpoints). + Linear, + /// Quadratic Bézier curve with a single control point. + Quadratic { + /// Index into [`PointDomain`] for the control handle. + handle: usize, + }, + /// Cubic Bézier curve with two control points. + Cubic { + /// Index into [`PointDomain`] for the handle near the start point. + handle_start: usize, + /// Index into [`PointDomain`] for the handle near the end point. + handle_end: usize, + }, +} + +impl Default for Curvature { + fn default() -> Self { + Self::Linear + } +} + +impl Curvature { + /// Returns `true` if this is a cubic curve. + pub fn is_cubic(&self) -> bool { + matches!(self, Self::Cubic { .. }) + } + + /// Returns `true` if this is a linear segment. + pub fn is_linear(&self) -> bool { + matches!(self, Self::Linear) + } + + /// Returns the start handle index, if present. + /// + /// For quadratic curves, this returns the single handle. + /// For cubic curves, this returns the handle near the start point. + pub fn start_handle(&self) -> Option { + match *self { + Self::Quadratic { handle } => Some(handle), + Self::Cubic { handle_start, .. } => Some(handle_start), + Self::Linear => None, + } + } + + /// Returns the end handle index, if present. + /// + /// Only cubic curves have an end handle. + pub fn end_handle(&self) -> Option { + match *self { + Self::Cubic { handle_end, .. } => Some(handle_end), + _ => None, + } + } + + /// Returns a new curvature with handle indices adjusted by an offset. + /// + /// Used when concatenating domains. + #[must_use] + pub fn offset_indices(self, offset: usize) -> Self { + match self { + Self::Linear => Self::Linear, + Self::Quadratic { handle } => Self::Quadratic { handle: handle + offset }, + Self::Cubic { handle_start, handle_end } => Self::Cubic { + handle_start: handle_start + offset, + handle_end: handle_end + offset, + }, + } + } + + /// Returns a new curvature with swapped handle indices (for reversing a segment). + #[must_use] + pub fn reversed(self) -> Self { + match self { + Self::Cubic { handle_start, handle_end } => Self::Cubic { + handle_start: handle_end, + handle_end: handle_start, + }, + other => other, + } + } +} + /// A no-op hasher that allows writing u64s (the id type). #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub struct NoHash(Option); @@ -1307,3 +1400,73 @@ pub struct IdMap { pub segment_map: HashMap, pub region_map: HashMap, } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn curvature_default_is_linear() { + assert_eq!(Curvature::default(), Curvature::Linear); + } + + #[test] + fn curvature_is_cubic() { + assert!(!Curvature::Linear.is_cubic()); + assert!(!Curvature::Quadratic { handle: 0 }.is_cubic()); + assert!(Curvature::Cubic { handle_start: 0, handle_end: 1 }.is_cubic()); + } + + #[test] + fn curvature_is_linear() { + assert!(Curvature::Linear.is_linear()); + assert!(!Curvature::Quadratic { handle: 0 }.is_linear()); + assert!(!Curvature::Cubic { handle_start: 0, handle_end: 1 }.is_linear()); + } + + #[test] + fn curvature_start_handle() { + assert_eq!(Curvature::Linear.start_handle(), None); + assert_eq!(Curvature::Quadratic { handle: 5 }.start_handle(), Some(5)); + assert_eq!(Curvature::Cubic { handle_start: 3, handle_end: 7 }.start_handle(), Some(3)); + } + + #[test] + fn curvature_end_handle() { + assert_eq!(Curvature::Linear.end_handle(), None); + assert_eq!(Curvature::Quadratic { handle: 5 }.end_handle(), None); + assert_eq!(Curvature::Cubic { handle_start: 3, handle_end: 7 }.end_handle(), Some(7)); + } + + #[test] + fn curvature_offset_indices() { + let offset = 10; + + assert_eq!(Curvature::Linear.offset_indices(offset), Curvature::Linear); + + assert_eq!( + Curvature::Quadratic { handle: 5 }.offset_indices(offset), + Curvature::Quadratic { handle: 15 } + ); + + assert_eq!( + Curvature::Cubic { handle_start: 3, handle_end: 7 }.offset_indices(offset), + Curvature::Cubic { handle_start: 13, handle_end: 17 } + ); + } + + #[test] + fn curvature_reversed() { + assert_eq!(Curvature::Linear.reversed(), Curvature::Linear); + + assert_eq!( + Curvature::Quadratic { handle: 5 }.reversed(), + Curvature::Quadratic { handle: 5 } + ); + + assert_eq!( + Curvature::Cubic { handle_start: 3, handle_end: 7 }.reversed(), + Curvature::Cubic { handle_start: 7, handle_end: 3 } + ); + } +}