From 66ec85a3c9092d012d5955bf2804c4bec25e05e4 Mon Sep 17 00:00:00 2001 From: 0HyperCube <78500760+0HyperCube@users.noreply.github.com> Date: Thu, 2 Mar 2023 16:48:09 +0000 Subject: [PATCH] =?UTF-8?q?B=C3=A9zier-rs:=20Add=20utils=20to=20subpath=20?= =?UTF-8?q?(#1058)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add utils to bezier-rs subpath * Apply code review changes * Remove tan from constant * Fix compile * Fix tests --- Cargo.lock | 2 + .../node_graph/node_graph_message_handler.rs | 2 +- editor/src/node_graph_executor.rs | 2 +- libraries/bezier-rs/Cargo.toml | 3 + libraries/bezier-rs/src/bezier/mod.rs | 12 ++ libraries/bezier-rs/src/subpath/core.rs | 117 ++++++++++++++++++ libraries/bezier-rs/src/subpath/mod.rs | 8 +- libraries/bezier-rs/src/subpath/structs.rs | 48 ++++++- libraries/bezier-rs/src/subpath/transform.rs | 15 +++ node-graph/gcore/src/uuid.rs | 3 +- node-graph/interpreted-executor/src/lib.rs | 2 +- .../other/bezier-rs-demos/wasm/src/subpath.rs | 2 +- 12 files changed, 206 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4c25face..b90912b1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -281,7 +281,9 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" name = "bezier-rs" version = "0.1.0" dependencies = [ + "dyn-any", "glam", + "serde", ] [[package]] diff --git a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs index 928c3baa..10d43f4c 100644 --- a/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs +++ b/editor/src/messages/portfolio/document/node_graph/node_graph_message_handler.rs @@ -281,7 +281,7 @@ impl NodeGraphMessageHandler { node_id: link_start, output_index: link_start_index, // TODO: add ui for lambdas - lambda, + lambda: _, } = *input { Some(FrontendNodeLink { diff --git a/editor/src/node_graph_executor.rs b/editor/src/node_graph_executor.rs index 2bd9161b..9051b90c 100644 --- a/editor/src/node_graph_executor.rs +++ b/editor/src/node_graph_executor.rs @@ -6,7 +6,7 @@ use crate::messages::prelude::*; use document_legacy::{document::pick_safe_imaginate_resolution, layers::layer_info::LayerDataType}; use document_legacy::{LayerId, Operation}; -use graph_craft::document::{generate_uuid, value::TaggedValue, NodeId, NodeInput, NodeNetwork, NodeOutput}; +use graph_craft::document::{generate_uuid, NodeId, NodeInput, NodeNetwork, NodeOutput}; use graph_craft::executor::Compiler; use graphene_core::raster::{Image, ImageFrame}; use interpreted_executor::executor::DynamicExecutor; diff --git a/libraries/bezier-rs/Cargo.toml b/libraries/bezier-rs/Cargo.toml index b81f4134..c1c5da95 100644 --- a/libraries/bezier-rs/Cargo.toml +++ b/libraries/bezier-rs/Cargo.toml @@ -15,3 +15,6 @@ documentation = "https://graphite.rs/bezier-rs-demos/" [dependencies] glam = { version = "0.22", features = ["serde"] } + +dyn-any = { path = "../dyn-any", optional = true } +serde = { version = "1.0", features = ["derive"], optional = true } diff --git a/libraries/bezier-rs/src/bezier/mod.rs b/libraries/bezier-rs/src/bezier/mod.rs index 1efa693b..52612509 100644 --- a/libraries/bezier-rs/src/bezier/mod.rs +++ b/libraries/bezier-rs/src/bezier/mod.rs @@ -15,6 +15,7 @@ use std::fmt::{Debug, Formatter, Result}; /// Representation of the handle point(s) in a bezier segment. #[derive(Copy, Clone, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] enum BezierHandles { Linear, /// Handles for a quadratic curve. @@ -31,8 +32,14 @@ enum BezierHandles { }, } +#[cfg(feature = "dyn-any")] +impl dyn_any::StaticType for BezierHandles { + type Static = BezierHandles; +} + /// Representation of a bezier curve with 2D points. #[derive(Copy, Clone, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Bezier { /// Start point of the bezier curve. start: DVec2, @@ -54,3 +61,8 @@ impl Debug for Bezier { debug_struct_ref.field("end", &self.end).finish() } } + +#[cfg(feature = "dyn-any")] +impl dyn_any::StaticType for Bezier { + type Static = Bezier; +} diff --git a/libraries/bezier-rs/src/subpath/core.rs b/libraries/bezier-rs/src/subpath/core.rs index cdc80fad..9347d77b 100644 --- a/libraries/bezier-rs/src/subpath/core.rs +++ b/libraries/bezier-rs/src/subpath/core.rs @@ -1,6 +1,7 @@ use super::*; use crate::consts::*; +use glam::DVec2; use std::fmt::Write; /// Functionality relating to core `Subpath` operations, such as constructors and `iter`. @@ -65,6 +66,11 @@ impl Subpath { SubpathIter { subpath: self, index: 0 } } + /// Returns a slice of the [ManipulatorGroup]s in the `Subpath`. + pub fn manipulator_groups(&self) -> &[ManipulatorGroup] { + &self.manipulator_groups + } + /// Appends to the `svg` mutable string with an SVG shape representation of the curve. pub fn curve_to_svg(&self, svg: &mut String, attributes: String) { let curve_start_argument = format!("{SVG_ARG_MOVE}{} {}", self[0].anchor.x, self[0].anchor.y); @@ -120,4 +126,115 @@ impl Subpath { self.handles_to_svg(svg, handle_attributes); } } + + /// Construct a [Subpath] from an iter of anchor positions. + pub fn from_anchors(anchor_positions: impl IntoIterator, closed: bool) -> Self { + Self::new(anchor_positions.into_iter().map(|anchor| ManipulatorGroup::new_anchor(anchor)).collect(), closed) + } + + /// Constructs a rectangle with `corner1` and `corner2` as the two corners. + pub fn new_rect(corner1: DVec2, corner2: DVec2) -> Self { + Self::from_anchors([corner1, DVec2::new(corner2.x, corner1.y), corner2, DVec2::new(corner1.x, corner2.y)], true) + } + + /// Constructs an elipse with `corner1` and `corner2` as the two corners of the bounding box. + pub fn new_ellipse(corner1: DVec2, corner2: DVec2) -> Self { + let size = (corner1 - corner2).abs(); + let center = (corner1 + corner2) / 2.; + let top = DVec2::new(center.x, corner1.y); + let bottom = DVec2::new(center.x, corner2.y); + let left = DVec2::new(corner1.x, center.y); + let right = DVec2::new(corner2.x, center.y); + + // Based on https://pomax.github.io/bezierinfo/#circles_cubic + const HANDLE_OFFSET_FACTOR: f64 = 0.551784777779014; + let handle_offset = size * HANDLE_OFFSET_FACTOR * 0.5; + + let manipulator_groups = vec![ + ManipulatorGroup::new(top, Some(top + handle_offset * DVec2::X), Some(top - handle_offset * DVec2::X)), + ManipulatorGroup::new(right, Some(right + handle_offset * DVec2::Y), Some(right - handle_offset * DVec2::Y)), + ManipulatorGroup::new(bottom, Some(bottom - handle_offset * DVec2::X), Some(bottom + handle_offset * DVec2::X)), + ManipulatorGroup::new(left, Some(left - handle_offset * DVec2::Y), Some(left + handle_offset * DVec2::Y)), + ]; + Self::new(manipulator_groups, true) + } + + /// Constructs a regular polygon (ngon). Based on `sides` and `radius`, which is the distance from the center to any vertex. + pub fn new_regular_polygon(center: DVec2, sides: u64, radius: f64) -> Self { + let anchor_positions = (0..sides).map(|i| { + let angle = (i as f64) * std::f64::consts::TAU / (sides as f64); + let center = center + DVec2::ONE * radius; + DVec2::new(center.x + radius * f64::cos(angle), center.y + radius * f64::sin(angle)) * 0.5 + }); + Self::from_anchors(anchor_positions, true) + } + + /// Constructs a line from `p1` to `p2` + pub fn new_line(p1: DVec2, p2: DVec2) -> Self { + Self::from_anchors([p1, p2], false) + } + + /// Construct a cubic spline from a list of points. + /// Based on https://mathworld.wolfram.com/CubicSpline.html + pub fn new_cubic_spline(points: Vec) -> Self { + // Number of points = number of points to find handles for + let len_points = points.len(); + + // matrix coefficients a, b and c (see https://mathworld.wolfram.com/CubicSpline.html) + // because the 'a' coefficients are all 1 they need not be stored + // this algorithm does a variation of the above algorithm. + // Instead of using the traditional cubic: a + bt + ct^2 + dt^3, we use the bezier cubic. + + let mut b = vec![DVec2::new(4., 4.); len_points]; + b[0] = DVec2::new(2., 2.); + b[len_points - 1] = DVec2::new(2., 2.); + + let mut c = vec![DVec2::new(1., 1.); len_points]; + + // 'd' is the the second point in a cubic bezier, which is what we solve for + let mut d = vec![DVec2::ZERO; len_points]; + + d[0] = DVec2::new(2. * points[1].x + points[0].x, 2. * points[1].y + points[0].y); + d[len_points - 1] = DVec2::new(3. * points[len_points - 1].x, 3. * points[len_points - 1].y); + for idx in 1..(len_points - 1) { + d[idx] = DVec2::new(4. * points[idx].x + 2. * points[idx + 1].x, 4. * points[idx].y + 2. * points[idx + 1].y); + } + + // Solve with Thomas algorithm (see https://en.wikipedia.org/wiki/Tridiagonal_matrix_algorithm) + // do row operations to eliminate `a` coefficients + c[0] /= -b[0]; + d[0] /= -b[0]; + #[allow(clippy::assign_op_pattern)] + for i in 1..len_points { + b[i] += c[i - 1]; + // for some reason the below line makes the borrow checker mad + //d[i] += d[i-1] + d[i] = d[i] + d[i - 1]; + c[i] /= -b[i]; + d[i] /= -b[i]; + } + + // at this point b[i] == -a[i + 1], a[i] == 0, + // do row operations to eliminate 'c' coefficients and solve + d[len_points - 1] *= -1.; + #[allow(clippy::assign_op_pattern)] + for i in (0..len_points - 1).rev() { + d[i] = d[i] - (c[i] * d[i + 1]); + d[i] *= -1.; //d[i] /= b[i] + } + + let mut subpath = Subpath::new(Vec::new(), false); + + // given the second point in the n'th cubic bezier, the third point is given by 2 * points[n+1] - b[n+1]. + // to find 'handle1_pos' for the n'th point we need the n-1 cubic bezier + subpath.manipulator_groups.push(ManipulatorGroup::new(points[0], None, Some(d[0]))); + for i in 1..len_points - 1 { + subpath.manipulator_groups.push(ManipulatorGroup::new(points[i], Some(2. * points[i] - d[i]), Some(d[i]))); + } + subpath + .manipulator_groups + .push(ManipulatorGroup::new(points[len_points - 1], Some(2. * points[len_points - 1] - d[len_points - 1]), None)); + + subpath + } } diff --git a/libraries/bezier-rs/src/subpath/mod.rs b/libraries/bezier-rs/src/subpath/mod.rs index 87b49c00..449d1104 100644 --- a/libraries/bezier-rs/src/subpath/mod.rs +++ b/libraries/bezier-rs/src/subpath/mod.rs @@ -12,12 +12,18 @@ use std::fmt::{Debug, Formatter, Result}; use std::ops::{Index, IndexMut}; /// Structure used to represent a path composed of [Bezier] curves. -#[derive(Clone, PartialEq)] +#[derive(Clone, PartialEq, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Subpath { manipulator_groups: Vec>, closed: bool, } +#[cfg(feature = "dyn-any")] +impl dyn_any::StaticType for Subpath { + type Static = Subpath; +} + /// Iteration structure for iterating across each curve of a `Subpath`, using an intermediate `Bezier` representation. pub struct SubpathIter<'a, ManipulatorGroupId: crate::Identifier> { index: usize, diff --git a/libraries/bezier-rs/src/subpath/structs.rs b/libraries/bezier-rs/src/subpath/structs.rs index 4a5a395c..e9537496 100644 --- a/libraries/bezier-rs/src/subpath/structs.rs +++ b/libraries/bezier-rs/src/subpath/structs.rs @@ -1,15 +1,18 @@ use super::Bezier; -use glam::DVec2; -use std::fmt::{Debug, Formatter, Result}; +use glam::{DAffine2, DVec2}; +use std::{ + fmt::{Debug, Formatter, Result}, + hash::Hash, +}; /// An id type used for each [ManipulatorGroup]. -pub trait Identifier: Sized + Clone + PartialEq { +pub trait Identifier: Sized + Clone + PartialEq + Hash + 'static { fn new() -> Self; } /// An empty id type for use in tests -#[derive(Clone, Copy, PartialEq, Eq)] +#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] #[cfg(test)] pub(crate) struct EmptyId; @@ -22,6 +25,7 @@ impl Identifier for EmptyId { /// Structure used to represent a single anchor with up to two optional associated handles along a `Subpath` #[derive(Copy, Clone, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct ManipulatorGroup { pub anchor: DVec2, pub in_handle: Option, @@ -29,6 +33,23 @@ pub struct ManipulatorGroup { pub id: ManipulatorGroupId, } +// TODO: Remove once we no longer need to hash floats in Graphite +impl Hash for ManipulatorGroup { + fn hash(&self, state: &mut H) { + self.anchor.to_array().iter().for_each(|x| x.to_bits().hash(state)); + self.in_handle.is_some().hash(state); + self.in_handle.map(|in_handle| in_handle.to_array().iter().for_each(|x| x.to_bits().hash(state))); + self.out_handle.is_some().hash(state); + self.out_handle.map(|out_handle| out_handle.to_array().iter().for_each(|x| x.to_bits().hash(state))); + self.id.hash(state); + } +} + +#[cfg(feature = "dyn-any")] +impl dyn_any::StaticType for ManipulatorGroup { + type Static = ManipulatorGroup; +} + impl Debug for ManipulatorGroup { fn fmt(&self, f: &mut Formatter<'_>) -> Result { f.debug_struct("ManipulatorGroup") @@ -40,6 +61,18 @@ impl Debug for ManipulatorGroup ManipulatorGroup { + /// Construct a new manipulator group from an anchor, in handle and out handle + pub fn new(anchor: DVec2, in_handle: Option, out_handle: Option) -> Self { + let id = ManipulatorGroupId::new(); + Self { anchor, in_handle, out_handle, id } + } + + /// Construct a new manipulator point with just an anchor position + pub fn new_anchor(anchor: DVec2) -> Self { + Self::new(anchor, Some(anchor), Some(anchor)) + } + + /// Create a bezier curve that starts at the current manipulator group and finishes in the `end_group` manipulator group. pub fn to_bezier(&self, end_group: &ManipulatorGroup) -> Bezier { let start = self.anchor; let end = end_group.anchor; @@ -52,4 +85,11 @@ impl ManipulatorGroup (None, None) => Bezier::from_linear_dvec2(start, end), } } + + /// Apply a transformation to all of the [ManipulatorGroup] points + pub fn apply_transform(&mut self, affine_transform: DAffine2) { + self.anchor = affine_transform.transform_point2(self.anchor); + self.in_handle = self.in_handle.map(|in_handle| affine_transform.transform_point2(in_handle)); + self.out_handle = self.out_handle.map(|out_handle| affine_transform.transform_point2(out_handle)); + } } diff --git a/libraries/bezier-rs/src/subpath/transform.rs b/libraries/bezier-rs/src/subpath/transform.rs index 7e28f0e4..bd037117 100644 --- a/libraries/bezier-rs/src/subpath/transform.rs +++ b/libraries/bezier-rs/src/subpath/transform.rs @@ -2,6 +2,8 @@ use super::*; use crate::utils::SubpathTValue; use crate::utils::TValue; +use glam::DAffine2; + /// Helper function to ensure the index and t value pair is mapped within a maximum index value. /// Allows for the point to be fetched without needing to handle an additional edge case. /// - Ex. Via `subpath.iter().nth(index).evaluate(t);` @@ -263,6 +265,13 @@ impl Subpath { closed: false, } } + + /// Apply a transformation to all of the [ManipulatorGroup]s in the [Subpath]. + pub fn apply_transform(&mut self, affine_transform: DAffine2) { + for manipulator_group in &mut self.manipulator_groups { + manipulator_group.apply_transform(affine_transform); + } + } } #[cfg(test)] @@ -719,4 +728,10 @@ mod tests { assert!(result.manipulator_groups[0].out_handle.is_none()); assert_eq!(result.manipulator_groups.len(), 1); } + + fn transform_subpath() { + let mut subpath = set_up_open_subpath(); + subpath.apply_transform(glam::DAffine2::IDENTITY); + assert_eq!(subpath, set_up_open_subpath()); + } } diff --git a/node-graph/gcore/src/uuid.rs b/node-graph/gcore/src/uuid.rs index b410eb45..79bd7428 100644 --- a/node-graph/gcore/src/uuid.rs +++ b/node-graph/gcore/src/uuid.rs @@ -70,7 +70,8 @@ mod uuid_generation { pub use uuid_generation::*; -#[derive(Clone, Copy, PartialEq, Eq)] +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct ManipulatorGroupId(u64); impl bezier_rs::Identifier for ManipulatorGroupId { diff --git a/node-graph/interpreted-executor/src/lib.rs b/node-graph/interpreted-executor/src/lib.rs index 2aca8d6d..c29c4393 100644 --- a/node-graph/interpreted-executor/src/lib.rs +++ b/node-graph/interpreted-executor/src/lib.rs @@ -153,7 +153,7 @@ mod tests { }; use crate::executor::DynamicExecutor; - use graph_craft::executor::{Compiler, Executor}; + use graph_craft::executor::Compiler; let compiler = Compiler {}; let protograph = compiler.compile_single(network, true).expect("Graph should be generated"); diff --git a/website/other/bezier-rs-demos/wasm/src/subpath.rs b/website/other/bezier-rs-demos/wasm/src/subpath.rs index 9f751520..fe9aa741 100644 --- a/website/other/bezier-rs-demos/wasm/src/subpath.rs +++ b/website/other/bezier-rs-demos/wasm/src/subpath.rs @@ -6,7 +6,7 @@ use glam::DVec2; use std::fmt::Write; use wasm_bindgen::prelude::*; -#[derive(Clone, PartialEq)] +#[derive(Clone, PartialEq, Hash)] pub(crate) struct EmptyId; impl bezier_rs::Identifier for EmptyId {