diff --git a/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs b/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs index 287faf00d1..994e47d636 100644 --- a/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs +++ b/editor/src/messages/tool/transform_layer/transform_layer_message_handler.rs @@ -176,7 +176,7 @@ impl MessageHandler> for TransformLayer return; } - if !using_path_tool { + if !using_path_tool || !using_shape_tool { *selected.pivot = selected.mean_average_of_pivots(); self.local_pivot = document.metadata().document_to_viewport.inverse().transform_point2(*selected.pivot); self.grab_target = document.metadata().document_to_viewport.inverse().transform_point2(selected.mean_average_of_pivots()); diff --git a/libraries/bezier-rs/src/subpath/core.rs b/libraries/bezier-rs/src/subpath/core.rs index a18550db6d..b38039e9ef 100644 --- a/libraries/bezier-rs/src/subpath/core.rs +++ b/libraries/bezier-rs/src/subpath/core.rs @@ -1,6 +1,6 @@ use super::*; -use crate::consts::*; use crate::utils::format_point; +use crate::{BezierHandles, consts::*}; use glam::DVec2; use std::fmt::Write; @@ -271,6 +271,55 @@ impl Subpath { ) } + pub fn spiral_point(theta: f64, a: f64, b: f64) -> DVec2 { + let r = a + b * theta; + DVec2::new(r * theta.cos(), -r * theta.sin()) + } + + fn spiral_tangent(theta: f64, a: f64, b: f64) -> DVec2 { + let r = a + b * theta; + let dx = b * theta.cos() - r * theta.sin(); + let dy = b * theta.sin() + r * theta.cos(); + DVec2::new(dx, -dy).normalize() + } + + pub fn wrap_angle(angle: f64) -> f64 { + (angle + std::f64::consts::PI).rem_euclid(2.0 * std::f64::consts::PI) - std::f64::consts::PI + } + + fn spiral_arc_length(theta: f64, a: f64, b: f64) -> f64 { + let r = a + b * theta; + let sqrt_term = (r * r + b * b).sqrt(); + (r * sqrt_term + b * b * ((r + sqrt_term).ln())) / (2.0 * b) + } + + pub fn generate_equal_arc_bezier_spiral2(a: f64, b: f64, turns: u32, delta_theta: f64, angle_offset: f64) -> Self { + let mut manipulator_groups = Vec::new(); + let mut prev_in_handle = None; + let mut theta = 0.; + let theta_end = angle_offset + turns as f64 * std::f64::consts::TAU; + + while theta < theta_end { + let theta_next = f64::min(theta + delta_theta, theta_end); + let p0 = Self::spiral_point(theta, a, b); + let p3 = Self::spiral_point(theta_next, a, b); + let t0 = Self::spiral_tangent(theta, a, b); + let t1 = Self::spiral_tangent(theta_next, a, b); + + let arc_len = Self::spiral_arc_length(theta_next, a, b) - Self::spiral_arc_length(theta, a, b); + let d = arc_len / 3.0; + + let p1 = p0 + d * t0; + let p2 = p3 - d * t1; + + manipulator_groups.push(ManipulatorGroup::new(p0, prev_in_handle, Some(p1))); + prev_in_handle = Some(p2); + theta = theta_next; + } + + Self::new(manipulator_groups, false) + } + /// Constructs an ellipse 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(); diff --git a/libraries/bezier-rs/src/utils.rs b/libraries/bezier-rs/src/utils.rs index 2e80b80e8e..ff1ca6db0a 100644 --- a/libraries/bezier-rs/src/utils.rs +++ b/libraries/bezier-rs/src/utils.rs @@ -302,6 +302,49 @@ pub fn format_point(svg: &mut String, prefix: &str, x: f64, y: f64) -> std::fmt: Ok(()) } +pub fn spiral_point(theta: f64, a: f64, b: f64) -> DVec2 { + let r = a + b * theta; + DVec2::new(r * theta.cos(), -r * theta.sin()) +} + +pub fn spiral_tangent(theta: f64, b: f64) -> DVec2 { + let dx = b * (theta.cos() - theta * theta.sin()); + let dy = b * (theta.sin() + theta * theta.cos()); + DVec2::new(dx, dy).normalize() +} + +pub fn wrap_angle(angle: f64) -> f64 { + (angle + std::f64::consts::PI).rem_euclid(2.0 * std::f64::consts::PI) - std::f64::consts::PI +} + +pub fn bezier_point(p0: DVec2, p1: DVec2, p2: DVec2, p3: DVec2, t: f64) -> DVec2 { + let u = 1.0 - t; + p0 * u * u * u + p1 * 3.0 * u * u * t + p2 * 3.0 * u * t * t + p3 * t * t * t +} + +pub fn bezier_derivative(p0: DVec2, p1: DVec2, p2: DVec2, p3: DVec2, t: f64) -> DVec2 { + let u = 1.0 - t; + -3.0 * u * u * p0 + 3.0 * (u * u - 2.0 * u * t) * p1 + 3.0 * (2.0 * u * t - t * t) * p2 + 3.0 * t * t * p3 +} + +pub fn esq_for_d(p0: DVec2, t0: DVec2, p3: DVec2, t1: DVec2, theta0: f64, theta1: f64, d: f64, a: f64, b: f64, samples: usize) -> f64 { + let p1 = p0 + d * t0; + let p2 = p3 - d * t1; + let mut total = 0.0; + for i in 1..samples { + let t = i as f64 / samples as f64; + let bez = bezier_point(p0, p1, p2, p3, t); + let bez_d = bezier_derivative(p0, p1, p2, p3, t); + let bez_angle = bez_d.y.atan2(bez_d.x); + + let theta = theta0 + (theta1 - theta0) * t; + let spiral_angle = theta; + let diff = wrap_angle(bez_angle - spiral_angle); + total += diff * diff * bez_d.length(); + } + total / samples as f64 +} + #[cfg(test)] mod tests { use super::*; diff --git a/node-graph/gcore/src/vector/generator_nodes.rs b/node-graph/gcore/src/vector/generator_nodes.rs index adab1ce531..ff8dff507d 100644 --- a/node-graph/gcore/src/vector/generator_nodes.rs +++ b/node-graph/gcore/src/vector/generator_nodes.rs @@ -1,3 +1,5 @@ +use std::f64::consts::{FRAC_PI_4, FRAC_PI_8, TAU}; + use super::misc::{ArcType, AsU64, GridType}; use super::{PointId, SegmentId, StrokeId}; use crate::Ctx; @@ -65,6 +67,28 @@ fn arc( ))) } +#[node_macro::node(category("Vector: Shape"))] +fn spiral( + _: impl Ctx, + _primary: (), + #[default(1.)] inner_radius: f64, + #[default(1.)] tightness: f64, + #[default(6)] + #[hard_min(1.)] + turns: u32, + #[default(0.)] + #[range((0., 360.))] + angle_offset: f64, +) -> VectorDataTable { + VectorDataTable::new(VectorData::from_subpath(Subpath::generate_equal_arc_bezier_spiral2( + inner_radius, + tightness, + turns, + FRAC_PI_4, + angle_offset.to_radians(), + ))) +} + #[node_macro::node(category("Vector: Shape"))] fn ellipse(_: impl Ctx, _primary: (), #[default(50)] radius_x: f64, #[default(25)] radius_y: f64) -> VectorDataTable { let radius = DVec2::new(radius_x, radius_y);