mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-19 19:08:05 +08:00
unified log and arc spiral into spiral node
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
use super::*;
|
||||
use crate::utils::format_point;
|
||||
use crate::utils::{format_point, spiral_arc_length, spiral_point, spiral_tangent, split_cubic_bezier};
|
||||
use crate::{BezierHandles, consts::*};
|
||||
use glam::DVec2;
|
||||
use std::fmt::Write;
|
||||
@@ -271,42 +271,7 @@ impl<PointId: crate::Identifier> Subpath<PointId> {
|
||||
)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
fn split_cubic_bezier(p0: DVec2, p1: DVec2, p2: DVec2, p3: DVec2, t: f64) -> (DVec2, DVec2, DVec2, DVec2) {
|
||||
let p01 = p0.lerp(p1, t);
|
||||
let p12 = p1.lerp(p2, t);
|
||||
let p23 = p2.lerp(p3, t);
|
||||
|
||||
let p012 = p01.lerp(p12, t);
|
||||
let p123 = p12.lerp(p23, t);
|
||||
|
||||
let p0123 = p012.lerp(p123, t); // final split point
|
||||
|
||||
(p0, p01, p012, p0123) // First half of the Bézier
|
||||
}
|
||||
|
||||
pub fn generate_equal_arc_bezier_spiral2(a: f64, b: f64, turns: f64, delta_theta: f64) -> Self {
|
||||
pub fn new_spiral(a: f64, b: f64, turns: f64, delta_theta: f64, spiral_type: SpiralType) -> Self {
|
||||
let mut manipulator_groups = Vec::new();
|
||||
let mut prev_in_handle = None;
|
||||
let theta_end = turns * std::f64::consts::TAU;
|
||||
@@ -315,12 +280,12 @@ impl<PointId: crate::Identifier> Subpath<PointId> {
|
||||
while theta < theta_end {
|
||||
let theta_next = theta + delta_theta;
|
||||
|
||||
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 p0 = spiral_point(theta, a, b, spiral_type);
|
||||
let p3 = spiral_point(theta_next, a, b, spiral_type);
|
||||
let t0 = spiral_tangent(theta, a, b, spiral_type);
|
||||
let t1 = spiral_tangent(theta_next, a, b, spiral_type);
|
||||
|
||||
let arc_len = Self::spiral_arc_length(theta_next, a, b) - Self::spiral_arc_length(theta, a, b);
|
||||
let arc_len = spiral_arc_length(theta, theta_next, a, b, spiral_type);
|
||||
let d = arc_len / 3.0;
|
||||
|
||||
let p1 = p0 + d * t0;
|
||||
@@ -329,65 +294,7 @@ impl<PointId: crate::Identifier> Subpath<PointId> {
|
||||
let is_last_segment = theta_next >= theta_end;
|
||||
if is_last_segment {
|
||||
let t = (theta_end - theta) / (theta_next - theta); // t in [0, 1]
|
||||
let (trim_p0, trim_p1, trim_p2, trim_p3) = Self::split_cubic_bezier(p0, p1, p2, p3, t);
|
||||
|
||||
manipulator_groups.push(ManipulatorGroup::new(trim_p0, prev_in_handle, Some(trim_p1)));
|
||||
prev_in_handle = Some(trim_p2);
|
||||
manipulator_groups.push(ManipulatorGroup::new(trim_p3, prev_in_handle, None));
|
||||
break;
|
||||
} else {
|
||||
manipulator_groups.push(ManipulatorGroup::new(p0, prev_in_handle, Some(p1)));
|
||||
prev_in_handle = Some(p2);
|
||||
}
|
||||
|
||||
theta = theta_next;
|
||||
}
|
||||
|
||||
Self::new(manipulator_groups, false)
|
||||
}
|
||||
|
||||
pub fn log_spiral_point(theta: f64, a: f64, b: f64) -> DVec2 {
|
||||
let r = a * (b * theta).exp(); // a * e^(bθ)
|
||||
DVec2::new(r * theta.cos(), -r * theta.sin())
|
||||
}
|
||||
|
||||
pub fn log_spiral_arc_length(theta_start: f64, theta_end: f64, a: f64, b: f64) -> f64 {
|
||||
let factor = (1. + b * b).sqrt();
|
||||
(a / b) * factor * ((b * theta_end).exp() - (b * theta_start).exp())
|
||||
}
|
||||
|
||||
pub fn log_spiral_tangent(theta: f64, a: f64, b: f64) -> DVec2 {
|
||||
let r = a * (b * theta).exp();
|
||||
let dx = r * (b * theta.cos() - theta.sin());
|
||||
let dy = r * (b * theta.sin() + theta.cos());
|
||||
|
||||
DVec2::new(dx, -dy).normalize()
|
||||
}
|
||||
|
||||
pub fn generate_logarithmic_spiral(a: f64, b: f64, turns: f64, delta_theta: f64) -> Self {
|
||||
let mut manipulator_groups = Vec::new();
|
||||
let mut prev_in_handle = None;
|
||||
let theta_end = turns * std::f64::consts::TAU;
|
||||
|
||||
let mut theta = 0.0;
|
||||
while theta < theta_end {
|
||||
let theta_next = theta + delta_theta;
|
||||
|
||||
let p0 = Self::log_spiral_point(theta, a, b);
|
||||
let p3 = Self::log_spiral_point(theta_next, a, b);
|
||||
let t0 = Self::log_spiral_tangent(theta, a, b);
|
||||
let t1 = Self::log_spiral_tangent(theta_next, a, b);
|
||||
|
||||
let arc_len = Self::log_spiral_arc_length(theta, theta_next, a, b);
|
||||
let d = arc_len / 3.0;
|
||||
|
||||
let p1 = p0 + d * t0;
|
||||
let p2 = p3 - d * t1;
|
||||
|
||||
let is_last_segment = theta_next >= theta_end;
|
||||
if is_last_segment {
|
||||
let t = (theta_end - theta) / (theta_next - theta); // t in [0, 1]
|
||||
let (trim_p0, trim_p1, trim_p2, trim_p3) = Self::split_cubic_bezier(p0, p1, p2, p3, t);
|
||||
let (trim_p0, trim_p1, trim_p2, trim_p3) = split_cubic_bezier(p0, p1, p2, p3, t);
|
||||
|
||||
manipulator_groups.push(ManipulatorGroup::new(trim_p0, prev_in_handle, Some(trim_p1)));
|
||||
prev_in_handle = Some(trim_p2);
|
||||
|
||||
@@ -144,3 +144,9 @@ pub enum ArcType {
|
||||
Closed,
|
||||
PieSlice,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub enum SpiralType {
|
||||
Archimedean,
|
||||
Logarithmic,
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::consts::{MAX_ABSOLUTE_DIFFERENCE, STRICT_MAX_ABSOLUTE_DIFFERENCE};
|
||||
use crate::{ManipulatorGroup, Subpath};
|
||||
use crate::{ManipulatorGroup, SpiralType, Subpath};
|
||||
use glam::{BVec2, DMat2, DVec2};
|
||||
use std::fmt::Write;
|
||||
|
||||
@@ -302,47 +302,91 @@ 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 {
|
||||
/// Returns a point on the given spiral type at angle `theta`.
|
||||
pub fn spiral_point(theta: f64, a: f64, b: f64, spiral_type: SpiralType) -> DVec2 {
|
||||
match spiral_type {
|
||||
SpiralType::Archimedean => archimedean_spiral_point(theta, a, b),
|
||||
SpiralType::Logarithmic => log_spiral_point(theta, a, b),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the tangent direction at angle `theta` for the given spiral type.
|
||||
pub fn spiral_tangent(theta: f64, a: f64, b: f64, spiral_type: SpiralType) -> DVec2 {
|
||||
match spiral_type {
|
||||
SpiralType::Archimedean => archimedean_spiral_tangent(theta, a, b),
|
||||
SpiralType::Logarithmic => log_spiral_tangent(theta, a, b),
|
||||
}
|
||||
}
|
||||
|
||||
/// Computes arc length between two angles for the given spiral type.
|
||||
pub fn spiral_arc_length(theta_start: f64, theta_end: f64, a: f64, b: f64, spiral_type: SpiralType) -> f64 {
|
||||
match spiral_type {
|
||||
SpiralType::Archimedean => archimedean_spiral_arc_length(theta_start, theta_end, a, b),
|
||||
SpiralType::Logarithmic => log_spiral_arc_length(theta_start, theta_end, a, b),
|
||||
}
|
||||
}
|
||||
|
||||
/// Splits a cubic Bézier curve at parameter `t`, returning the first half.
|
||||
pub fn split_cubic_bezier(p0: DVec2, p1: DVec2, p2: DVec2, p3: DVec2, t: f64) -> (DVec2, DVec2, DVec2, DVec2) {
|
||||
let p01 = p0.lerp(p1, t);
|
||||
let p12 = p1.lerp(p2, t);
|
||||
let p23 = p2.lerp(p3, t);
|
||||
|
||||
let p012 = p01.lerp(p12, t);
|
||||
let p123 = p12.lerp(p23, t);
|
||||
|
||||
// final split point
|
||||
let p0123 = p012.lerp(p123, t);
|
||||
|
||||
// First half of the Bézier
|
||||
(p0, p01, p012, p0123)
|
||||
}
|
||||
|
||||
/// Returns a point on a logarithmic spiral at angle `theta`.
|
||||
pub fn log_spiral_point(theta: f64, a: f64, b: f64) -> DVec2 {
|
||||
let r = a * (b * theta).exp(); // a * e^(bθ)
|
||||
DVec2::new(r * theta.cos(), -r * theta.sin())
|
||||
}
|
||||
|
||||
/// Computes arc length along a logarithmic spiral between two angles.
|
||||
pub fn log_spiral_arc_length(theta_start: f64, theta_end: f64, a: f64, b: f64) -> f64 {
|
||||
let factor = (1. + b * b).sqrt();
|
||||
(a / b) * factor * ((b * theta_end).exp() - (b * theta_start).exp())
|
||||
}
|
||||
|
||||
/// Returns the tangent direction of a logarithmic spiral at angle `theta`.
|
||||
pub fn log_spiral_tangent(theta: f64, a: f64, b: f64) -> DVec2 {
|
||||
let r = a * (b * theta).exp();
|
||||
let dx = r * (b * theta.cos() - theta.sin());
|
||||
let dy = r * (b * theta.sin() + theta.cos());
|
||||
|
||||
DVec2::new(dx, -dy).normalize()
|
||||
}
|
||||
|
||||
/// Returns a point on an Archimedean spiral at angle `theta`.
|
||||
pub fn archimedean_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()
|
||||
/// Returns the tangent direction of an Archimedean spiral at angle `theta`.
|
||||
pub fn archimedean_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
|
||||
/// Computes arc length along an Archimedean spiral between two angles.
|
||||
pub fn archimedean_spiral_arc_length(theta_start: f64, theta_end: f64, a: f64, b: f64) -> f64 {
|
||||
archimedean_spiral_arc_length_origin(theta_end, a, b) - archimedean_spiral_arc_length_origin(theta_start, a, b)
|
||||
}
|
||||
|
||||
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
|
||||
/// Computes arc length from origin to a point on Archimedean spiral at angle `theta`.
|
||||
pub fn archimedean_spiral_arc_length_origin(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)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
Reference in New Issue
Block a user