use glam::Vec2 as GlamVec2;
use kurbo::{BezPath, ParamCurve, ParamCurveArclen, ParamCurveDeriv, PathEl, Point, Vec2};
pub trait GeometryModifier {
fn modify(&self, path: &mut BezPath);
}
pub struct ZigZagModifier {
pub ridges: f32,
pub size: f32,
pub smooth: bool,
}
impl GeometryModifier for ZigZagModifier {
fn modify(&self, path: &mut BezPath) {
if self.ridges <= 0.0 || self.size == 0.0 {
return;
}
let mut subpaths = Vec::new();
let mut current_subpath = BezPath::new();
for el in path.elements() {
match el {
PathEl::MoveTo(p) => {
if !current_subpath.elements().is_empty() {
subpaths.push(current_subpath);
}
current_subpath = BezPath::new();
current_subpath.move_to(*p);
}
_ => {
current_subpath.push(*el);
}
}
}
if !current_subpath.elements().is_empty() {
subpaths.push(current_subpath);
}
let mut new_path = BezPath::new();
for sub in subpaths {
let len = sub_path_length(&sub);
if len == 0.0 {
continue;
}
let step = len / (self.ridges as f64);
let mut points = Vec::new();
let mut walker = PathWalker::new(&sub);
for i in 0..=(self.ridges as usize) {
let t_dist = (i as f64 * step as f64).min(len);
if let Some((pos, tangent)) = walker.sample(t_dist) {
let normal = Vec2::new(-tangent.y, tangent.x);
let dir = if i % 2 == 0 { 1.0 } else { -1.0 };
let offset = normal * (self.size as f64 * dir);
points.push(pos + offset);
}
}
if points.is_empty() {
continue;
}
new_path.move_to(points[0]);
if self.smooth {
for i in 1..points.len() {
let _prev = points[i - 1];
let curr = points[i];
new_path.line_to(curr);
}
} else {
for p in points.iter().skip(1) {
new_path.line_to(*p);
}
}
}
*path = new_path;
}
}
fn sub_path_length(path: &BezPath) -> f64 {
let walker = PathWalker::new(path);
walker.total_length
}
pub struct PuckerBloatModifier {
pub amount: f32, pub center: GlamVec2,
}
impl GeometryModifier for PuckerBloatModifier {
fn modify(&self, path: &mut BezPath) {
if self.amount == 0.0 {
return;
}
let center = Point::new(self.center.x as f64, self.center.y as f64);
let factor = self.amount / 100.0;
let mut new_path = BezPath::new();
let p_scale = 1.0 + factor as f64;
let c_scale = 1.0 - factor as f64;
for el in path.elements() {
match el {
PathEl::MoveTo(p) => {
let new_p = center + (*p - center) * p_scale;
new_path.move_to(new_p);
}
PathEl::LineTo(p) => {
let new_p = center + (*p - center) * p_scale;
new_path.line_to(new_p);
}
PathEl::CurveTo(p1, p2, p3) => {
let np1 = center + (*p1 - center) * c_scale;
let np2 = center + (*p2 - center) * c_scale;
let np3 = center + (*p3 - center) * p_scale;
new_path.curve_to(np1, np2, np3);
}
PathEl::QuadTo(p1, p2) => {
let np1 = center + (*p1 - center) * c_scale;
let np2 = center + (*p2 - center) * p_scale;
new_path.quad_to(np1, np2);
}
PathEl::ClosePath => {
new_path.close_path();
}
}
}
*path = new_path;
}
}
pub struct TwistModifier {
pub angle: f32, pub center: GlamVec2,
}
impl GeometryModifier for TwistModifier {
fn modify(&self, path: &mut BezPath) {
if self.angle == 0.0 {
return;
}
let center = Point::new(self.center.x as f64, self.center.y as f64);
let angle_rad = self.angle.to_radians() as f64;
let radius = 100.0;
let transform_point = |p: Point| -> Point {
let vec = p - center;
let dist = vec.hypot();
if dist < 0.001 {
return p;
}
let theta = angle_rad * (dist / radius);
let (sin, cos) = theta.sin_cos();
let rx = vec.x * cos - vec.y * sin;
let ry = vec.x * sin + vec.y * cos;
center + Vec2::new(rx, ry)
};
let mut new_path = BezPath::new();
for el in path.elements() {
match el {
PathEl::MoveTo(p) => new_path.move_to(transform_point(*p)),
PathEl::LineTo(p) => new_path.line_to(transform_point(*p)),
PathEl::CurveTo(p1, p2, p3) => new_path.curve_to(
transform_point(*p1),
transform_point(*p2),
transform_point(*p3),
),
PathEl::QuadTo(p1, p2) => {
new_path.quad_to(transform_point(*p1), transform_point(*p2))
}
PathEl::ClosePath => new_path.close_path(),
}
}
*path = new_path;
}
}
pub struct WiggleModifier {
pub seed: f32,
pub time: f32,
pub speed: f32, pub amount: f32, pub correlation: f32,
}
impl GeometryModifier for WiggleModifier {
fn modify(&self, path: &mut BezPath) {
if self.amount == 0.0 {
return;
}
let t = self.time * self.speed;
let mut new_path = BezPath::new();
let mut idx = 0;
let noise = |idx: usize, offset: f32| -> Vec2 {
let input = t + offset; let t_i = input.floor();
let t_f = input - t_i;
let h = |k: f32| -> f32 { ((k * 12.9898 + self.seed).sin() * 43758.5453).fract() };
let n1 = h(t_i);
let n2 = h(t_i + 1.0);
let _val = n1 + (n2 - n1) * t_f;
let hx = |k: f32| -> f32 {
((k * 12.9898 + self.seed + (idx as f32) * 1.1).sin() * 43758.5453).fract()
};
let hy = |k: f32| -> f32 {
((k * 78.233 + self.seed + (idx as f32) * 1.7).sin() * 43758.5453).fract()
};
let rx = hx(t_i) + (hx(t_i + 1.0) - hx(t_i)) * t_f;
let ry = hy(t_i) + (hy(t_i + 1.0) - hy(t_i)) * t_f;
Vec2::new((rx as f64 - 0.5) * 2.0, (ry as f64 - 0.5) * 2.0)
};
for el in path.elements() {
match el {
PathEl::MoveTo(p) => {
let d = noise(idx, 0.0) * self.amount as f64;
new_path.move_to(*p + d);
idx += 1;
}
PathEl::LineTo(p) => {
let d = noise(idx, 0.0) * self.amount as f64;
new_path.line_to(*p + d);
idx += 1;
}
PathEl::CurveTo(p1, p2, p3) => {
let d1 = noise(idx, 0.1) * self.amount as f64;
let d2 = noise(idx + 1, 0.2) * self.amount as f64; let d3 = noise(idx + 2, 0.0) * self.amount as f64;
new_path.curve_to(*p1 + d1, *p2 + d2, *p3 + d3);
idx += 3;
}
PathEl::QuadTo(p1, p2) => {
let d1 = noise(idx, 0.1) * self.amount as f64;
let d2 = noise(idx + 1, 0.0) * self.amount as f64;
new_path.quad_to(*p1 + d1, *p2 + d2);
idx += 2;
}
PathEl::ClosePath => {
new_path.close_path();
}
}
}
*path = new_path;
}
}
pub struct OffsetPathModifier {
pub amount: f32,
pub line_join: u8,
pub miter_limit: f32,
}
impl GeometryModifier for OffsetPathModifier {
fn modify(&self, _path: &mut BezPath) {
}
}
struct PathWalker<'a> {
path: &'a BezPath,
total_length: f64,
}
impl<'a> PathWalker<'a> {
fn new(path: &'a BezPath) -> Self {
let mut len = 0.0;
let mut last = Point::ZERO;
for el in path.elements() {
match el {
PathEl::MoveTo(p) => last = *p,
PathEl::LineTo(p) => {
len += p.distance(last);
last = *p;
}
PathEl::CurveTo(p1, p2, p3) => {
use kurbo::CubicBez;
let c = CubicBez::new(last, *p1, *p2, *p3);
len += c.arclen(0.1);
last = *p3;
}
PathEl::QuadTo(p1, p2) => {
use kurbo::QuadBez;
let q = QuadBez::new(last, *p1, *p2);
len += q.arclen(0.1);
last = *p2;
}
_ => {}
}
}
Self {
path,
total_length: len,
}
}
fn sample(&mut self, dist: f64) -> Option<(Point, Vec2)> {
let mut current_dist = 0.0;
let mut last = Point::ZERO;
for el in self.path.elements() {
match el {
PathEl::MoveTo(p) => last = *p,
PathEl::LineTo(p) => {
let seg_len = p.distance(last);
if current_dist + seg_len >= dist {
let t = (dist - current_dist) / seg_len;
let pos = last.lerp(*p, t);
let tangent = *p - last; let norm_tangent = tangent.normalize();
return Some((pos, norm_tangent));
}
current_dist += seg_len;
last = *p;
}
PathEl::CurveTo(p1, p2, p3) => {
use kurbo::CubicBez;
let c = CubicBez::new(last, *p1, *p2, *p3);
let seg_len = c.arclen(0.1);
if current_dist + seg_len >= dist {
let t = (dist - current_dist) / seg_len;
let pos = c.eval(t);
let deriv = c.deriv().eval(t);
let tangent = deriv.to_vec2().normalize();
return Some((pos, tangent));
}
current_dist += seg_len;
last = *p3;
}
PathEl::QuadTo(p1, p2) => {
use kurbo::QuadBez;
let q = QuadBez::new(last, *p1, *p2);
let seg_len = q.arclen(0.1);
if current_dist + seg_len >= dist {
let t = (dist - current_dist) / seg_len;
let pos = q.eval(t);
let deriv = q.deriv().eval(t);
let tangent = deriv.to_vec2().normalize();
return Some((pos, tangent));
}
current_dist += seg_len;
last = *p2;
}
_ => {}
}
}
None
}
}