1use glam::Vec2;
6use std::fmt;
7
8#[derive(Debug, Clone, Copy, PartialEq)]
11pub struct ControlPoint {
12 pub position: Vec2,
13 pub in_tangent: Vec2,
14 pub out_tangent: Vec2,
15 pub weight: f32,
16 pub broken_tangents: bool, pub corner: bool, }
19
20impl ControlPoint {
21 pub fn new(pos: Vec2) -> Self {
22 Self {
23 position: pos,
24 in_tangent: Vec2::new(-0.3, 0.0),
25 out_tangent: Vec2::new( 0.3, 0.0),
26 weight: 1.0,
27 broken_tangents: false,
28 corner: false,
29 }
30 }
31
32 pub fn corner(pos: Vec2) -> Self {
33 let mut cp = Self::new(pos);
34 cp.corner = true;
35 cp.broken_tangents = true;
36 cp
37 }
38
39 pub fn with_tangents(pos: Vec2, in_t: Vec2, out_t: Vec2) -> Self {
40 Self {
41 position: pos,
42 in_tangent: in_t,
43 out_tangent: out_t,
44 weight: 1.0,
45 broken_tangents: true,
46 corner: false,
47 }
48 }
49
50 pub fn in_handle_world(&self) -> Vec2 {
51 self.position + self.in_tangent
52 }
53
54 pub fn out_handle_world(&self) -> Vec2 {
55 self.position + self.out_tangent
56 }
57
58 pub fn set_out_tangent_smooth(&mut self, new_out: Vec2) {
60 self.out_tangent = new_out;
61 if !self.broken_tangents && !self.corner {
62 self.in_tangent = -new_out.normalize_or_zero() * self.in_tangent.length();
63 }
64 }
65
66 pub fn set_in_tangent_smooth(&mut self, new_in: Vec2) {
68 self.in_tangent = new_in;
69 if !self.broken_tangents && !self.corner {
70 self.out_tangent = -new_in.normalize_or_zero() * self.out_tangent.length();
71 }
72 }
73}
74
75#[derive(Debug, Clone, Copy, PartialEq, Eq)]
78pub enum CurveKind {
79 CubicBezier,
80 CatmullRom,
81 BSpline,
82 Hermite,
83 Linear,
84 Constant,
85 SineWave,
86 SawWave,
87 SquareWave,
88 TriangleWave,
89 Noise,
90 Spring,
91}
92
93impl CurveKind {
94 pub fn label(&self) -> &'static str {
95 match self {
96 Self::CubicBezier => "Cubic Bezier",
97 Self::CatmullRom => "Catmull-Rom",
98 Self::BSpline => "B-Spline",
99 Self::Hermite => "Hermite",
100 Self::Linear => "Linear",
101 Self::Constant => "Constant (Step)",
102 Self::SineWave => "Sine Wave",
103 Self::SawWave => "Sawtooth Wave",
104 Self::SquareWave => "Square Wave",
105 Self::TriangleWave => "Triangle Wave",
106 Self::Noise => "Noise",
107 Self::Spring => "Spring",
108 }
109 }
110
111 pub fn procedural(&self) -> bool {
112 matches!(self, Self::SineWave | Self::SawWave | Self::SquareWave
113 | Self::TriangleWave | Self::Noise | Self::Spring)
114 }
115}
116
117#[derive(Debug, Clone, Copy)]
120pub struct CurveSegment {
121 pub p0: Vec2,
122 pub p1: Vec2,
123 pub p2: Vec2,
124 pub p3: Vec2,
125}
126
127impl CurveSegment {
128 pub fn bezier(&self, t: f32) -> Vec2 {
130 let u = 1.0 - t;
131 self.p0 * (u*u*u)
132 + self.p1 * (3.0*u*u*t)
133 + self.p2 * (3.0*u*t*t)
134 + self.p3 * (t*t*t)
135 }
136
137 pub fn bezier_tangent(&self, t: f32) -> Vec2 {
138 let u = 1.0 - t;
139 (self.p1 - self.p0) * (3.0*u*u)
140 + (self.p2 - self.p1) * (6.0*u*t)
141 + (self.p3 - self.p2) * (3.0*t*t)
142 }
143
144 pub fn catmull_rom(&self, t: f32) -> Vec2 {
146 let t2 = t * t;
147 let t3 = t2 * t;
148 self.p0 * (-0.5*t3 + t2 - 0.5*t)
149 + self.p1 * (1.5*t3 - 2.5*t2 + 1.0)
150 + self.p2 * (-1.5*t3 + 2.0*t2 + 0.5*t)
151 + self.p3 * (0.5*t3 - 0.5*t2)
152 }
153
154 pub fn arc_length(&self, steps: u32) -> f32 {
156 let mut len = 0.0;
157 let mut prev = self.bezier(0.0);
158 for i in 1..=steps {
159 let t = i as f32 / steps as f32;
160 let cur = self.bezier(t);
161 len += (cur - prev).length();
162 prev = cur;
163 }
164 len
165 }
166
167 pub fn t_at_arc_length(&self, target_len: f32) -> f32 {
169 let total = self.arc_length(64);
170 if total < 1e-7 { return 0.0; }
171 let target = target_len.clamp(0.0, total);
172 let mut lo = 0.0f32;
173 let mut hi = 1.0f32;
174 for _ in 0..32 {
175 let mid = (lo + hi) * 0.5;
176 let seg = CurveSegment {
177 p0: self.p0, p1: self.p1, p2: self.p2, p3: self.p3,
178 };
179 let len = seg.arc_length_to(mid);
180 if (len - target).abs() < 1e-5 { return mid; }
181 if len < target { lo = mid; } else { hi = mid; }
182 }
183 (lo + hi) * 0.5
184 }
185
186 fn arc_length_to(&self, t_max: f32) -> f32 {
187 let steps = 32u32;
188 let mut len = 0.0;
189 let mut prev = self.bezier(0.0);
190 for i in 1..=steps {
191 let t = (i as f32 / steps as f32) * t_max;
192 let cur = self.bezier(t);
193 len += (cur - prev).length();
194 prev = cur;
195 }
196 len
197 }
198
199 pub fn closest_t(&self, p: Vec2) -> f32 {
201 let mut t = 0.5f32;
202 for _ in 0..8 {
203 let pt = self.bezier(t);
204 let dp = self.bezier_tangent(t);
205 let diff = pt - p;
206 let denom = dp.dot(dp);
207 if denom.abs() < 1e-8 { break; }
208 t -= diff.dot(dp) / denom;
209 t = t.clamp(0.0, 1.0);
210 }
211 t
212 }
213}
214
215#[derive(Debug, Clone)]
218pub struct CurveData {
219 pub kind: CurveKind,
220 pub points: Vec<ControlPoint>,
221 pub closed: bool,
222 pub clamp_x: Option<(f32, f32)>,
223 pub clamp_y: Option<(f32, f32)>,
224 pub frequency: f32,
226 pub amplitude: f32,
227 pub phase: f32,
228 pub offset: f32,
229 pub noise_seed: u32,
230 pub spring_stiffness: f32,
231 pub spring_damping: f32,
232}
233
234impl CurveData {
235 pub fn new(kind: CurveKind) -> Self {
236 let mut data = Self {
237 kind,
238 points: Vec::new(),
239 closed: false,
240 clamp_x: None,
241 clamp_y: None,
242 frequency: 1.0,
243 amplitude: 1.0,
244 phase: 0.0,
245 offset: 0.0,
246 noise_seed: 42,
247 spring_stiffness: 8.0,
248 spring_damping: 0.5,
249 };
250 if !kind.procedural() {
252 data.points.push(ControlPoint::new(Vec2::new(0.0, 0.0)));
253 data.points.push(ControlPoint::new(Vec2::new(1.0, 1.0)));
254 }
255 data
256 }
257
258 pub fn linear() -> Self {
259 let mut c = Self::new(CurveKind::Linear);
260 c.points[0] = ControlPoint::new(Vec2::new(0.0, 0.0));
261 c.points[1] = ControlPoint::new(Vec2::new(1.0, 1.0));
262 c
263 }
264
265 pub fn ease_in_out() -> Self {
266 let mut c = Self::new(CurveKind::CubicBezier);
267 c.points[0] = ControlPoint::with_tangents(
268 Vec2::new(0.0, 0.0),
269 Vec2::new(-0.1, 0.0),
270 Vec2::new(0.3, 0.0),
271 );
272 c.points[1] = ControlPoint::with_tangents(
273 Vec2::new(1.0, 1.0),
274 Vec2::new(-0.3, 0.0),
275 Vec2::new(0.1, 0.0),
276 );
277 c
278 }
279
280 pub fn bounce() -> Self {
281 let mut c = Self::new(CurveKind::CubicBezier);
282 c.points.clear();
283 c.points.push(ControlPoint::new(Vec2::new(0.0, 0.0)));
284 c.points.push(ControlPoint::with_tangents(
285 Vec2::new(0.4, 1.0),
286 Vec2::new(-0.1, 0.3),
287 Vec2::new(0.1, 0.3),
288 ));
289 c.points.push(ControlPoint::with_tangents(
290 Vec2::new(0.7, 0.5),
291 Vec2::new(-0.05, 0.15),
292 Vec2::new(0.05, 0.15),
293 ));
294 c.points.push(ControlPoint::new(Vec2::new(1.0, 1.0)));
295 c
296 }
297
298 pub fn add_point(&mut self, pos: Vec2) {
299 let idx = self.points.partition_point(|p| p.position.x < pos.x);
301 self.points.insert(idx, ControlPoint::new(pos));
302 }
303
304 pub fn remove_point(&mut self, idx: usize) {
305 if self.points.len() > 2 {
306 self.points.remove(idx);
307 }
308 }
309
310 pub fn segment_count(&self) -> usize {
311 if self.points.len() < 2 { return 0; }
312 if self.closed {
313 self.points.len()
314 } else {
315 self.points.len() - 1
316 }
317 }
318
319 pub fn build_segment(&self, i: usize) -> CurveSegment {
320 let n = self.points.len();
321 let p0 = &self.points[i % n];
322 let p1 = &self.points[(i + 1) % n];
323 CurveSegment {
324 p0: p0.position,
325 p1: p0.out_handle_world(),
326 p2: p1.in_handle_world(),
327 p3: p1.position,
328 }
329 }
330
331 pub fn evaluate(&self, x: f32) -> f32 {
333 match self.kind {
334 CurveKind::Linear => self.eval_linear(x),
335 CurveKind::Constant => self.eval_constant(x),
336 CurveKind::CubicBezier => self.eval_bezier(x),
337 CurveKind::CatmullRom => self.eval_catmull(x),
338 CurveKind::BSpline => self.eval_bspline(x),
339 CurveKind::Hermite => self.eval_bezier(x), CurveKind::SineWave => self.eval_sine(x),
341 CurveKind::SawWave => self.eval_saw(x),
342 CurveKind::SquareWave => self.eval_square(x),
343 CurveKind::TriangleWave => self.eval_triangle(x),
344 CurveKind::Noise => self.eval_noise(x),
345 CurveKind::Spring => self.eval_spring(x),
346 }
347 }
348
349 fn eval_linear(&self, x: f32) -> f32 {
350 if self.points.len() < 2 { return 0.0; }
351 let seg = self.find_segment(x);
352 if let Some((p0, p1, t)) = seg {
353 p0.y + (p1.y - p0.y) * t
354 } else {
355 self.points.last().unwrap().position.y
356 }
357 }
358
359 fn eval_constant(&self, x: f32) -> f32 {
360 if self.points.is_empty() { return 0.0; }
361 for (i, p) in self.points.iter().enumerate() {
362 if p.position.x > x {
363 return if i == 0 {
364 self.points[0].position.y
365 } else {
366 self.points[i - 1].position.y
367 };
368 }
369 }
370 self.points.last().unwrap().position.y
371 }
372
373 fn eval_bezier(&self, x: f32) -> f32 {
374 let n = self.points.len();
375 if n < 2 { return 0.0; }
376 if x <= self.points[0].position.x { return self.points[0].position.y; }
377 if x >= self.points[n-1].position.x { return self.points[n-1].position.y; }
378
379 for i in 0..n-1 {
381 let p0 = &self.points[i];
382 let p1 = &self.points[i+1];
383 if x >= p0.position.x && x <= p1.position.x {
384 let seg = self.build_segment(i);
385 let mut lo = 0.0f32;
387 let mut hi = 1.0f32;
388 for _ in 0..32 {
389 let mid = (lo + hi) * 0.5;
390 let bx = seg.bezier(mid).x;
391 if (bx - x).abs() < 1e-5 {
392 return seg.bezier(mid).y;
393 }
394 if bx < x { lo = mid; } else { hi = mid; }
395 }
396 return seg.bezier((lo + hi) * 0.5).y;
397 }
398 }
399 self.points.last().unwrap().position.y
400 }
401
402 fn eval_catmull(&self, x: f32) -> f32 {
403 let n = self.points.len();
404 if n < 2 { return 0.0; }
405 if x <= self.points[0].position.x { return self.points[0].position.y; }
406 if x >= self.points[n-1].position.x { return self.points[n-1].position.y; }
407
408 for i in 0..n-1 {
409 let p0 = &self.points[i];
410 let p1 = &self.points[i+1];
411 if x >= p0.position.x && x <= p1.position.x {
412 let dx = p1.position.x - p0.position.x;
413 if dx < 1e-7 { return p0.position.y; }
414 let t = (x - p0.position.x) / dx;
415 let prev = if i > 0 { self.points[i-1].position } else {
416 p0.position - (p1.position - p0.position)
417 };
418 let next = if i+2 < n { self.points[i+2].position } else {
419 p1.position + (p1.position - p0.position)
420 };
421 let seg = CurveSegment {
422 p0: prev, p1: p0.position, p2: p1.position, p3: next,
423 };
424 return seg.catmull_rom(t).y;
425 }
426 }
427 self.points.last().unwrap().position.y
428 }
429
430 fn eval_bspline(&self, x: f32) -> f32 {
431 let n = self.points.len();
433 if n < 2 { return 0.0; }
434 self.eval_linear(x) }
436
437 fn eval_sine(&self, x: f32) -> f32 {
438 self.offset + self.amplitude
439 * (2.0 * std::f32::consts::PI * self.frequency * x + self.phase).sin()
440 }
441
442 fn eval_saw(&self, x: f32) -> f32 {
443 let t = (self.frequency * x + self.phase / (2.0 * std::f32::consts::PI)).fract();
444 self.offset + self.amplitude * (2.0 * t - 1.0)
445 }
446
447 fn eval_square(&self, x: f32) -> f32 {
448 let t = (self.frequency * x + self.phase / (2.0 * std::f32::consts::PI)).fract();
449 self.offset + self.amplitude * if t < 0.5 { 1.0 } else { -1.0 }
450 }
451
452 fn eval_triangle(&self, x: f32) -> f32 {
453 let t = (self.frequency * x + self.phase / (2.0 * std::f32::consts::PI)).fract();
454 let v = if t < 0.5 { 4.0*t - 1.0 } else { 3.0 - 4.0*t };
455 self.offset + self.amplitude * v
456 }
457
458 fn eval_noise(&self, x: f32) -> f32 {
459 let xi = (x * self.frequency + self.noise_seed as f32).floor() as i32;
461 let xf = (x * self.frequency + self.noise_seed as f32).fract();
462 let h0 = Self::hash(xi) as f32 / u32::MAX as f32;
463 let h1 = Self::hash(xi + 1) as f32 / u32::MAX as f32;
464 let t = xf * xf * (3.0 - 2.0 * xf);
465 self.offset + self.amplitude * (h0 + (h1 - h0) * t)
466 }
467
468 fn hash(x: i32) -> u32 {
469 let mut h = x as u32 ^ 0x9e3779b9u32;
470 h = h.wrapping_mul(0x85ebca6b);
471 h ^= h >> 13;
472 h = h.wrapping_mul(0xc2b2ae35);
473 h ^= h >> 16;
474 h
475 }
476
477 fn eval_spring(&self, x: f32) -> f32 {
478 let omega = (self.spring_stiffness - self.spring_damping * self.spring_damping).abs().sqrt();
480 let envelope = (-self.spring_damping * x).exp();
481 self.offset + self.amplitude * (1.0 - envelope * (omega * x).cos())
482 }
483
484 fn find_segment(&self, x: f32) -> Option<(Vec2, Vec2, f32)> {
485 let n = self.points.len();
486 for i in 0..n-1 {
487 let p0 = self.points[i].position;
488 let p1 = self.points[i+1].position;
489 if x >= p0.x && x <= p1.x {
490 let dx = p1.x - p0.x;
491 let t = if dx > 1e-7 { (x - p0.x) / dx } else { 0.5 };
492 return Some((p0, p1, t));
493 }
494 }
495 None
496 }
497
498 pub fn sample_uniform(&self, count: usize) -> Vec<Vec2> {
500 (0..count).map(|i| {
501 let x = i as f32 / (count - 1).max(1) as f32;
502 Vec2::new(x, self.evaluate(x))
503 }).collect()
504 }
505
506 pub fn bounds(&self) -> (Vec2, Vec2) {
507 if self.points.is_empty() {
508 return (Vec2::ZERO, Vec2::ONE);
509 }
510 let mut min_pt = self.points[0].position;
511 let mut max_pt = self.points[0].position;
512 for p in &self.points {
513 min_pt = min_pt.min(p.position);
514 max_pt = max_pt.max(p.position);
515 min_pt = min_pt.min(p.in_handle_world());
516 max_pt = max_pt.max(p.in_handle_world());
517 min_pt = min_pt.min(p.out_handle_world());
518 max_pt = max_pt.max(p.out_handle_world());
519 }
520 (min_pt, max_pt)
521 }
522
523 pub fn to_polyline(&self, steps_per_segment: u32) -> Vec<Vec2> {
525 if self.kind.procedural() {
526 return self.sample_uniform(steps_per_segment as usize * 4);
527 }
528 let n = self.segment_count();
529 let mut pts = Vec::with_capacity(n * steps_per_segment as usize + 1);
530 for seg_i in 0..n {
531 let seg = self.build_segment(seg_i);
532 for step in 0..steps_per_segment {
533 let t = step as f32 / steps_per_segment as f32;
534 let pt = match self.kind {
535 CurveKind::CatmullRom => seg.catmull_rom(t),
536 _ => seg.bezier(t),
537 };
538 pts.push(pt);
539 }
540 }
541 if let Some(last) = self.points.last() {
542 pts.push(last.position);
543 }
544 pts
545 }
546}
547
548#[derive(Debug, Clone, Copy, PartialEq, Eq)]
551pub enum ChannelType {
552 Generic,
553 PositionX,
554 PositionY,
555 PositionZ,
556 RotationX,
557 RotationY,
558 RotationZ,
559 ScaleX,
560 ScaleY,
561 ScaleZ,
562 ColorR,
563 ColorG,
564 ColorB,
565 ColorA,
566 Weight,
567 Custom,
568}
569
570impl ChannelType {
571 pub fn label(&self) -> &'static str {
572 match self {
573 Self::Generic => "Value",
574 Self::PositionX => "X",
575 Self::PositionY => "Y",
576 Self::PositionZ => "Z",
577 Self::RotationX => "Rx",
578 Self::RotationY => "Ry",
579 Self::RotationZ => "Rz",
580 Self::ScaleX => "Sx",
581 Self::ScaleY => "Sy",
582 Self::ScaleZ => "Sz",
583 Self::ColorR => "R",
584 Self::ColorG => "G",
585 Self::ColorB => "B",
586 Self::ColorA => "A",
587 Self::Weight => "W",
588 Self::Custom => "Custom",
589 }
590 }
591
592 pub fn color(&self) -> [f32; 3] {
593 match self {
594 Self::PositionX | Self::RotationX | Self::ScaleX | Self::ColorR
595 => [0.9, 0.3, 0.2],
596 Self::PositionY | Self::RotationY | Self::ScaleY | Self::ColorG
597 => [0.3, 0.9, 0.2],
598 Self::PositionZ | Self::RotationZ | Self::ScaleZ | Self::ColorB
599 => [0.2, 0.4, 0.9],
600 Self::ColorA | Self::Weight
601 => [0.7, 0.7, 0.7],
602 _ => [0.8, 0.8, 0.0],
603 }
604 }
605}
606
607#[derive(Debug, Clone)]
608pub struct CurveChannel {
609 pub name: String,
610 pub channel_type: ChannelType,
611 pub curve: CurveData,
612 pub enabled: bool,
613 pub locked: bool,
614 pub solo: bool,
615 pub min_value: f32,
616 pub max_value: f32,
617}
618
619impl CurveChannel {
620 pub fn new(name: String, ch: ChannelType) -> Self {
621 Self {
622 name,
623 channel_type: ch,
624 curve: CurveData::linear(),
625 enabled: true,
626 locked: false,
627 solo: false,
628 min_value: -1.0,
629 max_value: 1.0,
630 }
631 }
632
633 pub fn evaluate(&self, x: f32) -> f32 {
634 if !self.enabled { return 0.0; }
635 self.curve.evaluate(x)
636 }
637}
638
639#[derive(Debug, Clone)]
642pub struct CurveSet {
643 pub name: String,
644 pub channels: Vec<CurveChannel>,
645 pub time_range: (f32, f32),
646 pub value_range: (f32, f32),
647}
648
649impl CurveSet {
650 pub fn new(name: String) -> Self {
651 Self {
652 name,
653 channels: Vec::new(),
654 time_range: (0.0, 1.0),
655 value_range: (-1.0, 1.0),
656 }
657 }
658
659 pub fn for_vec3(name: String) -> Self {
660 let mut cs = Self::new(name.clone());
661 cs.channels.push(CurveChannel::new(format!("{}.X", name), ChannelType::PositionX));
662 cs.channels.push(CurveChannel::new(format!("{}.Y", name), ChannelType::PositionY));
663 cs.channels.push(CurveChannel::new(format!("{}.Z", name), ChannelType::PositionZ));
664 cs
665 }
666
667 pub fn for_color(name: String) -> Self {
668 let mut cs = Self::new(name.clone());
669 cs.channels.push(CurveChannel::new(format!("{}.R", name), ChannelType::ColorR));
670 cs.channels.push(CurveChannel::new(format!("{}.G", name), ChannelType::ColorG));
671 cs.channels.push(CurveChannel::new(format!("{}.B", name), ChannelType::ColorB));
672 cs.channels.push(CurveChannel::new(format!("{}.A", name), ChannelType::ColorA));
673 cs
674 }
675
676 pub fn evaluate_at(&self, t: f32) -> Vec<f32> {
677 self.channels.iter().map(|c| c.evaluate(t)).collect()
678 }
679
680 pub fn add_channel(&mut self, ch: CurveChannel) {
681 self.channels.push(ch);
682 }
683
684 pub fn auto_fit_ranges(&mut self) {
685 let mut min_x = f32::MAX;
686 let mut max_x = f32::MIN;
687 let mut min_y = f32::MAX;
688 let mut max_y = f32::MIN;
689 for ch in &self.channels {
690 let (lo, hi) = ch.curve.bounds();
691 min_x = min_x.min(lo.x);
692 max_x = max_x.max(hi.x);
693 min_y = min_y.min(lo.y);
694 max_y = max_y.max(hi.y);
695 }
696 let pad_x = (max_x - min_x) * 0.05;
697 let pad_y = (max_y - min_y) * 0.1;
698 self.time_range = (min_x - pad_x, max_x + pad_x);
699 self.value_range = (min_y - pad_y, max_y + pad_y);
700 }
701}
702
703#[derive(Debug, Clone, Copy, PartialEq, Eq)]
706pub enum SelectionKind {
707 Point,
708 InHandle,
709 OutHandle,
710}
711
712#[derive(Debug, Clone, Copy, PartialEq, Eq)]
713pub struct PointSelection {
714 pub channel: usize,
715 pub point: usize,
716 pub kind: SelectionKind,
717}
718
719#[derive(Debug, Clone)]
722pub enum CurveEditAction {
723 MovePoint { channel: usize, point: usize, from: Vec2, to: Vec2 },
724 MoveInHandle { channel: usize, point: usize, from: Vec2, to: Vec2 },
725 MoveOutHandle { channel: usize, point: usize, from: Vec2, to: Vec2 },
726 AddPoint { channel: usize, pos: Vec2 },
727 RemovePoint { channel: usize, index: usize, data: ControlPoint },
728 SetCurveKind { channel: usize, from: CurveKind, to: CurveKind },
729 SetProceduralParam { channel: usize, param: String, from: f32, to: f32 },
730}
731
732impl fmt::Display for CurveEditAction {
733 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
734 match self {
735 Self::MovePoint { .. } => write!(f, "Move Point"),
736 Self::AddPoint { .. } => write!(f, "Add Point"),
737 Self::RemovePoint { .. } => write!(f, "Remove Point"),
738 Self::MoveInHandle { .. } => write!(f, "Move In Handle"),
739 Self::MoveOutHandle { .. } => write!(f, "Move Out Handle"),
740 Self::SetCurveKind { to, .. } => write!(f, "Set Curve Kind: {}", to.label()),
741 Self::SetProceduralParam { param, .. } => write!(f, "Set {}", param),
742 }
743 }
744}
745
746#[derive(Debug)]
749pub struct CurveEditor {
750 pub curve_set: CurveSet,
751 pub visible_channels: Vec<bool>,
752 pub selected_points: Vec<PointSelection>,
753 pub canvas_time_range: (f32, f32),
754 pub canvas_value_range: (f32, f32),
755 pub canvas_size: Vec2,
756 pub grid_lines_x: u32,
757 pub grid_lines_y: u32,
758 pub show_tangent_handles: bool,
759 pub show_reference_line: bool,
760 pub reference_value: f32,
761 pub snap_x: bool,
762 pub snap_y: bool,
763 pub snap_x_step: f32,
764 pub snap_y_step: f32,
765 pub dragging: Option<PointSelection>,
766 pub drag_start_pos: Vec2,
767 undo_stack: Vec<CurveEditAction>,
768 redo_stack: Vec<CurveEditAction>,
769 pub frame_time: f32,
770 pub play_cursor: f32,
771 pub looping: bool,
772}
773
774impl CurveEditor {
775 pub fn new(canvas_w: f32, canvas_h: f32) -> Self {
776 let cs = CurveSet::new("Curve".into());
777 Self {
778 curve_set: cs,
779 visible_channels: Vec::new(),
780 selected_points: Vec::new(),
781 canvas_time_range: (0.0, 1.0),
782 canvas_value_range: (-1.0, 1.0),
783 canvas_size: Vec2::new(canvas_w, canvas_h),
784 grid_lines_x: 10,
785 grid_lines_y: 8,
786 show_tangent_handles: true,
787 show_reference_line: false,
788 reference_value: 0.0,
789 snap_x: false,
790 snap_y: false,
791 snap_x_step: 0.1,
792 snap_y_step: 0.1,
793 dragging: None,
794 drag_start_pos: Vec2::ZERO,
795 undo_stack: Vec::new(),
796 redo_stack: Vec::new(),
797 frame_time: 0.0,
798 play_cursor: 0.0,
799 looping: true,
800 }
801 }
802
803 pub fn load_set(&mut self, set: CurveSet) {
804 self.visible_channels = vec![true; set.channels.len()];
805 self.canvas_time_range = set.time_range;
806 self.canvas_value_range = set.value_range;
807 self.curve_set = set;
808 self.selected_points.clear();
809 }
810
811 pub fn canvas_to_curve(&self, cx: f32, cy: f32) -> Vec2 {
813 let (t0, t1) = self.canvas_time_range;
814 let (v0, v1) = self.canvas_value_range;
815 Vec2::new(
816 t0 + (cx / self.canvas_size.x) * (t1 - t0),
817 v1 - (cy / self.canvas_size.y) * (v1 - v0),
818 )
819 }
820
821 pub fn curve_to_canvas(&self, tx: f32, ty: f32) -> Vec2 {
823 let (t0, t1) = self.canvas_time_range;
824 let (v0, v1) = self.canvas_value_range;
825 Vec2::new(
826 (tx - t0) / (t1 - t0) * self.canvas_size.x,
827 (v1 - ty) / (v1 - v0) * self.canvas_size.y,
828 )
829 }
830
831 pub fn add_point(&mut self, channel: usize, pos: Vec2) {
832 let pos = self.snapped(pos);
833 if let Some(ch) = self.curve_set.channels.get_mut(channel) {
834 ch.curve.add_point(pos);
835 self.undo_stack.push(CurveEditAction::AddPoint { channel, pos });
836 self.redo_stack.clear();
837 }
838 }
839
840 pub fn remove_point(&mut self, channel: usize, idx: usize) {
841 if let Some(ch) = self.curve_set.channels.get_mut(channel) {
842 let cp = ch.curve.points[idx];
843 ch.curve.remove_point(idx);
844 self.undo_stack.push(CurveEditAction::RemovePoint { channel, index: idx, data: cp });
845 self.redo_stack.clear();
846 }
847 }
848
849 pub fn move_point(&mut self, sel: PointSelection, new_pos: Vec2) {
850 let new_pos = self.snapped(new_pos);
851 if let Some(ch) = self.curve_set.channels.get_mut(sel.channel) {
852 if let Some(pt) = ch.curve.points.get_mut(sel.point) {
853 let from = pt.position;
854 match sel.kind {
855 SelectionKind::Point => {
856 let delta = new_pos - pt.position;
857 pt.position = new_pos;
858 pt.in_tangent += delta; pt.out_tangent += delta;
860 self.undo_stack.push(CurveEditAction::MovePoint {
861 channel: sel.channel, point: sel.point,
862 from, to: new_pos,
863 });
864 }
865 SelectionKind::InHandle => {
866 let from_h = pt.in_tangent;
867 pt.set_in_tangent_smooth(new_pos - pt.position);
868 self.undo_stack.push(CurveEditAction::MoveInHandle {
869 channel: sel.channel, point: sel.point,
870 from: from_h, to: pt.in_tangent,
871 });
872 }
873 SelectionKind::OutHandle => {
874 let from_h = pt.out_tangent;
875 pt.set_out_tangent_smooth(new_pos - pt.position);
876 self.undo_stack.push(CurveEditAction::MoveOutHandle {
877 channel: sel.channel, point: sel.point,
878 from: from_h, to: pt.out_tangent,
879 });
880 }
881 }
882 self.redo_stack.clear();
883 }
884 }
885 }
886
887 fn snapped(&self, pos: Vec2) -> Vec2 {
888 Vec2::new(
889 if self.snap_x { (pos.x / self.snap_x_step).round() * self.snap_x_step } else { pos.x },
890 if self.snap_y { (pos.y / self.snap_y_step).round() * self.snap_y_step } else { pos.y },
891 )
892 }
893
894 pub fn set_kind(&mut self, channel: usize, kind: CurveKind) {
895 if let Some(ch) = self.curve_set.channels.get_mut(channel) {
896 let from = ch.curve.kind;
897 ch.curve.kind = kind;
898 self.undo_stack.push(CurveEditAction::SetCurveKind { channel, from, to: kind });
899 self.redo_stack.clear();
900 }
901 }
902
903 pub fn auto_smooth_tangents(&mut self, channel: usize) {
904 if let Some(ch) = self.curve_set.channels.get_mut(channel) {
905 let n = ch.curve.points.len();
906 for i in 0..n {
907 let prev = if i > 0 { ch.curve.points[i-1].position }
908 else { ch.curve.points[i].position };
909 let next = if i+1 < n { ch.curve.points[i+1].position }
910 else { ch.curve.points[i].position };
911 let tangent = (next - prev) * 0.3;
912 ch.curve.points[i].out_tangent = tangent;
913 ch.curve.points[i].in_tangent = -tangent;
914 ch.curve.points[i].broken_tangents = false;
915 }
916 }
917 }
918
919 pub fn flatten_tangents(&mut self, channel: usize) {
920 if let Some(ch) = self.curve_set.channels.get_mut(channel) {
921 for pt in &mut ch.curve.points {
922 let out_len = pt.out_tangent.length();
923 let in_len = pt.in_tangent.length();
924 pt.out_tangent = Vec2::new(out_len, 0.0);
925 pt.in_tangent = Vec2::new(-in_len, 0.0);
926 }
927 }
928 }
929
930 pub fn select_all(&mut self) {
931 self.selected_points.clear();
932 for (ci, ch) in self.curve_set.channels.iter().enumerate() {
933 for pi in 0..ch.curve.points.len() {
934 self.selected_points.push(PointSelection {
935 channel: ci, point: pi, kind: SelectionKind::Point,
936 });
937 }
938 }
939 }
940
941 pub fn deselect_all(&mut self) {
942 self.selected_points.clear();
943 }
944
945 pub fn undo(&mut self) {
946 if let Some(action) = self.undo_stack.pop() {
947 match &action {
949 CurveEditAction::MovePoint { channel, point, from, .. } => {
950 let ch = *channel; let pt = *point; let f = *from;
951 if let Some(c) = self.curve_set.channels.get_mut(ch) {
952 if let Some(p) = c.curve.points.get_mut(pt) {
953 p.position = f;
954 }
955 }
956 }
957 CurveEditAction::RemovePoint { channel, index, data } => {
958 let ch = *channel; let i = *index; let d = *data;
959 if let Some(c) = self.curve_set.channels.get_mut(ch) {
960 c.curve.points.insert(i, d);
961 }
962 }
963 CurveEditAction::AddPoint { channel, .. } => {
964 let ch = *channel;
965 if let Some(c) = self.curve_set.channels.get_mut(ch) {
966 c.curve.points.pop();
967 }
968 }
969 _ => {}
970 }
971 self.redo_stack.push(action);
972 }
973 }
974
975 pub fn redo(&mut self) {
976 if let Some(action) = self.redo_stack.pop() {
977 self.undo_stack.push(action);
978 }
979 }
980
981 pub fn advance_play_cursor(&mut self, dt: f32) {
982 let (t0, t1) = self.canvas_time_range;
983 self.play_cursor += dt;
984 if self.looping && self.play_cursor > t1 {
985 self.play_cursor = t0;
986 }
987 self.play_cursor = self.play_cursor.clamp(t0, t1);
988 }
989
990 pub fn evaluate_all_at_cursor(&self) -> Vec<f32> {
991 self.curve_set.evaluate_at(self.play_cursor)
992 }
993
994 pub fn frame_all(&mut self) {
995 self.curve_set.auto_fit_ranges();
996 let pad_t = (self.curve_set.time_range.1 - self.curve_set.time_range.0) * 0.05;
997 let pad_v = (self.curve_set.value_range.1 - self.curve_set.value_range.0) * 0.1;
998 self.canvas_time_range = (
999 self.curve_set.time_range.0 - pad_t,
1000 self.curve_set.time_range.1 + pad_t,
1001 );
1002 self.canvas_value_range = (
1003 self.curve_set.value_range.0 - pad_v,
1004 self.curve_set.value_range.1 + pad_v,
1005 );
1006 }
1007
1008 pub fn zoom(&mut self, factor: f32, center_t: f32, center_v: f32) {
1009 let (t0, t1) = self.canvas_time_range;
1010 let (v0, v1) = self.canvas_value_range;
1011 let ht = (t1 - t0) * 0.5 / factor;
1012 let hv = (v1 - v0) * 0.5 / factor;
1013 self.canvas_time_range = (center_t - ht, center_t + ht);
1014 self.canvas_value_range = (center_v - hv, center_v + hv);
1015 }
1016
1017 pub fn pan(&mut self, dt: f32, dv: f32) {
1018 self.canvas_time_range = (self.canvas_time_range.0 + dt, self.canvas_time_range.1 + dt);
1019 self.canvas_value_range = (self.canvas_value_range.0 + dv, self.canvas_value_range.1 + dv);
1020 }
1021
1022 pub fn build_render_lines(&self) -> Vec<(usize, Vec<Vec2>)> {
1024 self.curve_set.channels.iter().enumerate()
1025 .filter(|(i, _)| self.visible_channels.get(*i).copied().unwrap_or(true))
1026 .map(|(i, ch)| {
1027 let pts = ch.curve.to_polyline(64);
1028 let canvas_pts = pts.iter().map(|p| self.curve_to_canvas(p.x, p.y)).collect();
1029 (i, canvas_pts)
1030 })
1031 .collect()
1032 }
1033}
1034
1035#[cfg(test)]
1038mod tests {
1039 use super::*;
1040
1041 #[test]
1042 fn linear_interpolation() {
1043 let c = CurveData::linear();
1044 assert!((c.evaluate(0.0) - 0.0).abs() < 0.001);
1045 assert!((c.evaluate(1.0) - 1.0).abs() < 0.001);
1046 assert!((c.evaluate(0.5) - 0.5).abs() < 0.01);
1047 }
1048
1049 #[test]
1050 fn bezier_endpoints() {
1051 let c = CurveData::ease_in_out();
1052 assert!((c.evaluate(0.0) - 0.0).abs() < 0.01);
1053 assert!((c.evaluate(1.0) - 1.0).abs() < 0.01);
1054 }
1055
1056 #[test]
1057 fn sine_wave_zero_crossing() {
1058 let c = CurveData::new(CurveKind::SineWave);
1059 let v = c.evaluate(0.0);
1060 assert!(v.abs() < 0.1);
1061 }
1062
1063 #[test]
1064 fn constant_step() {
1065 let mut c = CurveData::new(CurveKind::Constant);
1066 c.points[0] = ControlPoint::new(Vec2::new(0.0, 0.0));
1067 c.points[1] = ControlPoint::new(Vec2::new(1.0, 1.0));
1068 assert!((c.evaluate(0.4) - 0.0).abs() < 0.001);
1069 assert!((c.evaluate(1.0) - 1.0).abs() < 0.001);
1070 }
1071
1072 #[test]
1073 fn canvas_curve_roundtrip() {
1074 let ed = CurveEditor::new(800.0, 400.0);
1075 let t = 0.6f32;
1076 let v = 0.3f32;
1077 let c = ed.curve_to_canvas(t, v);
1078 let back = ed.canvas_to_curve(c.x, c.y);
1079 assert!((back.x - t).abs() < 1e-4);
1080 assert!((back.y - v).abs() < 1e-4);
1081 }
1082
1083 #[test]
1084 fn add_remove_point() {
1085 let mut c = CurveData::linear();
1086 c.add_point(Vec2::new(0.5, 0.7));
1087 assert_eq!(c.points.len(), 3);
1088 c.remove_point(1);
1089 assert_eq!(c.points.len(), 2);
1090 }
1091
1092 #[test]
1093 fn spring_curve_settles_near_one() {
1094 let c = CurveData::new(CurveKind::Spring);
1095 let v = c.evaluate(10.0);
1096 assert!((v - 1.0).abs() < 0.1, "spring value at t=10: {}", v);
1098 }
1099}