1use serde::{Deserialize, Serialize};
11
12#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
16pub enum BrushFalloff {
17 Linear,
19 Smooth,
21 Sharp,
23 Flat,
25}
26
27impl BrushFalloff {
28 pub fn evaluate(self, t: f64) -> f64 {
31 let t = t.clamp(0.0, 1.0);
32 match self {
33 Self::Linear => 1.0 - t,
34 Self::Smooth => {
35 let u = 1.0 - t;
36 u * u * (3.0 - 2.0 * u)
38 }
39 Self::Sharp => {
40 let u = 1.0 - t;
41 u * u * u
42 }
43 Self::Flat => 1.0,
44 }
45 }
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize)]
50pub struct PaintBrush {
51 pub radius: f64,
53 pub falloff: BrushFalloff,
55 pub strength: f64,
57}
58
59impl Default for PaintBrush {
60 fn default() -> Self {
61 Self {
62 radius: 0.05,
63 falloff: BrushFalloff::Smooth,
64 strength: 1.0,
65 }
66 }
67}
68
69#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
73pub enum MirrorAxis {
74 X,
75 Y,
76 Z,
77}
78
79impl MirrorAxis {
80 #[inline]
82 pub fn idx(self) -> usize {
83 match self {
84 Self::X => 0,
85 Self::Y => 1,
86 Self::Z => 2,
87 }
88 }
89}
90
91#[derive(Debug, Clone, Serialize, Deserialize)]
95pub struct MorphTargetData {
96 pub name: String,
98 pub deltas: Vec<[f64; 3]>,
100 pub sparse_indices: Vec<usize>,
102 pub sparse_deltas: Vec<[f64; 3]>,
104}
105
106pub struct DeltaPainter {
113 vertex_count: usize,
114 deltas: Vec<[f64; 3]>,
115 mask: Vec<f64>,
117}
118
119impl DeltaPainter {
120 pub fn new(vertex_count: usize) -> Self {
122 Self {
123 vertex_count,
124 deltas: vec![[0.0; 3]; vertex_count],
125 mask: vec![1.0; vertex_count],
126 }
127 }
128
129 #[inline]
131 pub fn vertex_count(&self) -> usize {
132 self.vertex_count
133 }
134
135 pub fn paint_at(
141 &mut self,
142 center_vertex: usize,
143 delta: [f64; 3],
144 brush: &PaintBrush,
145 vertex_positions: &[[f64; 3]],
146 ) -> anyhow::Result<()> {
147 if center_vertex >= self.vertex_count {
148 anyhow::bail!(
149 "center_vertex {} out of range (vertex_count = {})",
150 center_vertex,
151 self.vertex_count
152 );
153 }
154 if vertex_positions.len() < self.vertex_count {
155 anyhow::bail!(
156 "vertex_positions length {} < vertex_count {}",
157 vertex_positions.len(),
158 self.vertex_count
159 );
160 }
161 if brush.radius <= 0.0 {
162 anyhow::bail!("brush radius must be positive, got {}", brush.radius);
163 }
164
165 let center_pos = vertex_positions[center_vertex];
166 let radius_sq = brush.radius * brush.radius;
167
168 for (i, vpos) in vertex_positions.iter().enumerate().take(self.vertex_count) {
169 let dx = vpos[0] - center_pos[0];
170 let dy = vpos[1] - center_pos[1];
171 let dz = vpos[2] - center_pos[2];
172 let dist_sq = dx * dx + dy * dy + dz * dz;
173 if dist_sq > radius_sq {
174 continue;
175 }
176 let dist = dist_sq.sqrt();
177 let t = dist / brush.radius;
178 let influence = brush.falloff.evaluate(t) * brush.strength;
179 self.deltas[i][0] += delta[0] * influence;
180 self.deltas[i][1] += delta[1] * influence;
181 self.deltas[i][2] += delta[2] * influence;
182 }
183 Ok(())
184 }
185
186 pub fn paint_stroke(
190 &mut self,
191 vertices: &[usize],
192 delta: [f64; 3],
193 brush: &PaintBrush,
194 vertex_positions: &[[f64; 3]],
195 ) -> anyhow::Result<()> {
196 for &v in vertices {
197 self.paint_at(v, delta, brush, vertex_positions)?;
198 }
199 Ok(())
200 }
201
202 pub fn set_mask(&mut self, vertex: usize, value: f64) -> anyhow::Result<()> {
206 if vertex >= self.vertex_count {
207 anyhow::bail!(
208 "vertex {} out of range (vertex_count = {})",
209 vertex,
210 self.vertex_count
211 );
212 }
213 self.mask[vertex] = value.clamp(0.0, 1.0);
214 Ok(())
215 }
216
217 pub fn mask_vertex_group(&mut self, vertices: &[usize], value: f64) {
219 let clamped = value.clamp(0.0, 1.0);
220 for &v in vertices {
221 if v < self.vertex_count {
222 self.mask[v] = clamped;
223 }
224 }
225 }
226
227 pub fn clear_mask(&mut self) {
229 for m in &mut self.mask {
230 *m = 1.0;
231 }
232 }
233
234 pub fn invert_mask(&mut self) {
236 for m in &mut self.mask {
237 *m = 1.0 - *m;
238 }
239 }
240
241 pub fn mirror(
249 &mut self,
250 axis: MirrorAxis,
251 vertex_positions: &[[f64; 3]],
252 tolerance: f64,
253 ) -> anyhow::Result<()> {
254 if vertex_positions.len() < self.vertex_count {
255 anyhow::bail!(
256 "vertex_positions length {} < vertex_count {}",
257 vertex_positions.len(),
258 self.vertex_count
259 );
260 }
261 if tolerance <= 0.0 {
262 anyhow::bail!("tolerance must be positive, got {}", tolerance);
263 }
264
265 let ax = axis.idx();
266 let tol_sq = tolerance * tolerance;
267 let original_deltas = self.deltas.clone();
268
269 for i in 0..self.vertex_count {
271 let pos = vertex_positions[i];
272 if pos[ax] < 0.0 {
274 continue;
275 }
276
277 let mut mirror_pos = pos;
279 mirror_pos[ax] = -mirror_pos[ax];
280
281 let mut best_j: Option<usize> = None;
283 let mut best_dist_sq = f64::MAX;
284 for (j, jpos) in vertex_positions.iter().enumerate().take(self.vertex_count) {
285 let dp0 = jpos[0] - mirror_pos[0];
286 let dp1 = jpos[1] - mirror_pos[1];
287 let dp2 = jpos[2] - mirror_pos[2];
288 let dsq = dp0 * dp0 + dp1 * dp1 + dp2 * dp2;
289 if dsq < best_dist_sq {
290 best_dist_sq = dsq;
291 best_j = Some(j);
292 }
293 }
294
295 if let Some(j) = best_j {
296 if best_dist_sq <= tol_sq {
297 let mut d = original_deltas[i];
298 d[ax] = -d[ax]; self.deltas[j] = d;
300 }
301 }
302 }
303 Ok(())
304 }
305
306 pub fn smooth(&mut self, iterations: usize, adjacency: &[Vec<usize>]) -> anyhow::Result<()> {
312 if adjacency.len() < self.vertex_count {
313 anyhow::bail!(
314 "adjacency length {} < vertex_count {}",
315 adjacency.len(),
316 self.vertex_count
317 );
318 }
319 for _ in 0..iterations {
320 let prev = self.deltas.clone();
321 for (i, nbrs) in adjacency[..self.vertex_count].iter().enumerate() {
322 if nbrs.is_empty() {
323 continue;
324 }
325 let mut avg = [0.0_f64; 3];
326 let mut count = 0usize;
327 for &nb in nbrs {
328 if nb < self.vertex_count {
329 avg[0] += prev[nb][0];
330 avg[1] += prev[nb][1];
331 avg[2] += prev[nb][2];
332 count += 1;
333 }
334 }
335 if count > 0 {
336 let c = count as f64;
337 self.deltas[i] = [avg[0] / c, avg[1] / c, avg[2] / c];
338 }
339 }
340 }
341 Ok(())
342 }
343
344 pub fn get_deltas(&self) -> Vec<[f64; 3]> {
348 self.deltas
349 .iter()
350 .zip(self.mask.iter())
351 .map(|(d, &m)| [d[0] * m, d[1] * m, d[2] * m])
352 .collect()
353 }
354
355 pub fn raw_deltas(&self) -> &[[f64; 3]] {
357 &self.deltas
358 }
359
360 pub fn mask(&self) -> &[f64] {
362 &self.mask
363 }
364
365 pub fn clear(&mut self) {
367 for d in &mut self.deltas {
368 *d = [0.0; 3];
369 }
370 }
371
372 pub fn to_morph_target(&self, name: &str) -> MorphTargetData {
374 let deltas = self.get_deltas();
375 let threshold = 1e-12;
376 let mut sparse_indices = Vec::new();
377 let mut sparse_deltas = Vec::new();
378 for (i, d) in deltas.iter().enumerate() {
379 let mag_sq = d[0] * d[0] + d[1] * d[1] + d[2] * d[2];
380 if mag_sq > threshold * threshold {
381 sparse_indices.push(i);
382 sparse_deltas.push(*d);
383 }
384 }
385 MorphTargetData {
386 name: name.to_owned(),
387 deltas,
388 sparse_indices,
389 sparse_deltas,
390 }
391 }
392
393 pub fn set_delta(&mut self, vertex: usize, delta: [f64; 3]) -> anyhow::Result<()> {
395 if vertex >= self.vertex_count {
396 anyhow::bail!(
397 "vertex {} out of range (vertex_count = {})",
398 vertex,
399 self.vertex_count
400 );
401 }
402 self.deltas[vertex] = delta;
403 Ok(())
404 }
405
406 pub fn add_delta(&mut self, vertex: usize, delta: [f64; 3]) -> anyhow::Result<()> {
408 if vertex >= self.vertex_count {
409 anyhow::bail!(
410 "vertex {} out of range (vertex_count = {})",
411 vertex,
412 self.vertex_count
413 );
414 }
415 self.deltas[vertex][0] += delta[0];
416 self.deltas[vertex][1] += delta[1];
417 self.deltas[vertex][2] += delta[2];
418 Ok(())
419 }
420
421 pub fn scale_all(&mut self, factor: f64) {
423 for d in &mut self.deltas {
424 d[0] *= factor;
425 d[1] *= factor;
426 d[2] *= factor;
427 }
428 }
429
430 pub fn blend_from(&mut self, other: &DeltaPainter, weight: f64) -> anyhow::Result<()> {
432 if other.vertex_count != self.vertex_count {
433 anyhow::bail!(
434 "vertex_count mismatch: self={}, other={}",
435 self.vertex_count,
436 other.vertex_count
437 );
438 }
439 let w = weight.clamp(0.0, 1.0);
440 for i in 0..self.vertex_count {
441 self.deltas[i][0] += other.deltas[i][0] * w;
442 self.deltas[i][1] += other.deltas[i][1] * w;
443 self.deltas[i][2] += other.deltas[i][2] * w;
444 }
445 Ok(())
446 }
447}
448
449#[cfg(test)]
452mod tests {
453 use super::*;
454
455 fn simple_positions() -> Vec<[f64; 3]> {
456 vec![
457 [0.0, 0.0, 0.0],
458 [1.0, 0.0, 0.0],
459 [0.0, 1.0, 0.0],
460 [1.0, 1.0, 0.0],
461 ]
462 }
463
464 #[test]
465 fn test_new_painter() {
466 let p = DeltaPainter::new(10);
467 assert_eq!(p.vertex_count(), 10);
468 assert_eq!(p.raw_deltas().len(), 10);
469 assert_eq!(p.mask().len(), 10);
470 for d in p.raw_deltas() {
471 assert_eq!(*d, [0.0; 3]);
472 }
473 for &m in p.mask() {
474 assert!((m - 1.0).abs() < 1e-15);
475 }
476 }
477
478 #[test]
479 fn test_paint_at_center() {
480 let positions = simple_positions();
481 let mut p = DeltaPainter::new(4);
482 let brush = PaintBrush {
483 radius: 0.5,
484 falloff: BrushFalloff::Flat,
485 strength: 1.0,
486 };
487 p.paint_at(0, [0.0, 0.0, 1.0], &brush, &positions)
488 .expect("paint_at should succeed");
489
490 let d = p.raw_deltas()[0];
492 assert!((d[2] - 1.0).abs() < 1e-10);
493
494 let d1 = p.raw_deltas()[1];
496 assert!(d1[2].abs() < 1e-10);
497 }
498
499 #[test]
500 fn test_paint_at_out_of_range() {
501 let positions = simple_positions();
502 let mut p = DeltaPainter::new(4);
503 let brush = PaintBrush::default();
504 let result = p.paint_at(10, [0.0, 0.0, 1.0], &brush, &positions);
505 assert!(result.is_err());
506 }
507
508 #[test]
509 fn test_paint_stroke() {
510 let positions = simple_positions();
511 let mut p = DeltaPainter::new(4);
512 let brush = PaintBrush {
513 radius: 0.01,
514 falloff: BrushFalloff::Flat,
515 strength: 1.0,
516 };
517 p.paint_stroke(&[0, 1], [0.0, 1.0, 0.0], &brush, &positions)
518 .expect("paint_stroke should succeed");
519
520 assert!(p.raw_deltas()[0][1] > 0.5);
522 assert!(p.raw_deltas()[1][1] > 0.5);
523 }
524
525 #[test]
526 fn test_mask_blocks_output() {
527 let mut p = DeltaPainter::new(3);
528 p.set_delta(0, [1.0, 0.0, 0.0]).expect("set ok");
529 p.set_delta(1, [1.0, 0.0, 0.0]).expect("set ok");
530 p.set_mask(0, 0.0).expect("mask ok");
531 p.set_mask(1, 0.5).expect("mask ok");
532
533 let out = p.get_deltas();
534 assert!(out[0][0].abs() < 1e-15, "masked vertex should be zero");
535 assert!((out[1][0] - 0.5).abs() < 1e-10, "half-masked should be 0.5");
536 assert!((out[2][0]).abs() < 1e-15, "untouched vertex should be zero");
537 }
538
539 #[test]
540 fn test_mask_vertex_group() {
541 let mut p = DeltaPainter::new(5);
542 p.mask_vertex_group(&[1, 3], 0.25);
543 assert!((p.mask()[1] - 0.25).abs() < 1e-15);
544 assert!((p.mask()[3] - 0.25).abs() < 1e-15);
545 assert!((p.mask()[0] - 1.0).abs() < 1e-15);
546 }
547
548 #[test]
549 fn test_invert_mask() {
550 let mut p = DeltaPainter::new(3);
551 p.set_mask(0, 0.0).expect("ok");
552 p.set_mask(1, 0.3).expect("ok");
553 p.invert_mask();
554 assert!((p.mask()[0] - 1.0).abs() < 1e-15);
555 assert!((p.mask()[1] - 0.7).abs() < 1e-10);
556 assert!((p.mask()[2] - 0.0).abs() < 1e-15);
557 }
558
559 #[test]
560 fn test_smooth_reduces_peak() {
561 let mut p = DeltaPainter::new(3);
562 p.set_delta(0, [10.0, 0.0, 0.0]).expect("ok");
563 let adj = vec![vec![1], vec![0, 2], vec![1]];
565 let before = p.raw_deltas()[0][0];
566 p.smooth(1, &adj).expect("smooth ok");
567 let after = p.raw_deltas()[0][0];
568 assert!(after < before, "smoothing should reduce peak");
569 }
570
571 #[test]
572 fn test_mirror_x() {
573 let positions = vec![[-1.0, 0.0, 0.0], [1.0, 0.0, 0.0]];
575 let mut p = DeltaPainter::new(2);
576 p.set_delta(1, [0.5, 0.3, 0.1]).expect("ok");
577 p.mirror(MirrorAxis::X, &positions, 0.1).expect("mirror ok");
578 let d0 = p.raw_deltas()[0];
580 assert!(
581 (d0[0] - (-0.5)).abs() < 1e-10,
582 "X component should be negated"
583 );
584 assert!((d0[1] - 0.3).abs() < 1e-10);
585 assert!((d0[2] - 0.1).abs() < 1e-10);
586 }
587
588 #[test]
589 fn test_to_morph_target_sparse() {
590 let mut p = DeltaPainter::new(5);
591 p.set_delta(1, [1.0, 0.0, 0.0]).expect("ok");
592 p.set_delta(3, [0.0, 2.0, 0.0]).expect("ok");
593 let mt = p.to_morph_target("test_sparse");
594 assert_eq!(mt.name, "test_sparse");
595 assert_eq!(mt.deltas.len(), 5);
596 assert_eq!(mt.sparse_indices.len(), 2);
597 assert_eq!(mt.sparse_deltas.len(), 2);
598 assert!(mt.sparse_indices.contains(&1));
599 assert!(mt.sparse_indices.contains(&3));
600 }
601
602 #[test]
603 fn test_clear() {
604 let mut p = DeltaPainter::new(3);
605 p.set_delta(0, [1.0, 2.0, 3.0]).expect("ok");
606 p.clear();
607 for d in p.raw_deltas() {
608 assert_eq!(*d, [0.0; 3]);
609 }
610 }
611
612 #[test]
613 fn test_scale_all() {
614 let mut p = DeltaPainter::new(2);
615 p.set_delta(0, [1.0, 2.0, 3.0]).expect("ok");
616 p.scale_all(0.5);
617 let d = p.raw_deltas()[0];
618 assert!((d[0] - 0.5).abs() < 1e-15);
619 assert!((d[1] - 1.0).abs() < 1e-15);
620 assert!((d[2] - 1.5).abs() < 1e-15);
621 }
622
623 #[test]
624 fn test_blend_from() {
625 let mut a = DeltaPainter::new(3);
626 a.set_delta(0, [1.0, 0.0, 0.0]).expect("ok");
627 let mut b = DeltaPainter::new(3);
628 b.set_delta(0, [0.0, 2.0, 0.0]).expect("ok");
629 a.blend_from(&b, 0.5).expect("blend ok");
630 let d = a.raw_deltas()[0];
631 assert!((d[0] - 1.0).abs() < 1e-15);
632 assert!((d[1] - 1.0).abs() < 1e-15);
633 }
634
635 #[test]
636 fn test_brush_falloff_linear() {
637 assert!((BrushFalloff::Linear.evaluate(0.0) - 1.0).abs() < 1e-15);
638 assert!((BrushFalloff::Linear.evaluate(1.0)).abs() < 1e-15);
639 assert!((BrushFalloff::Linear.evaluate(0.5) - 0.5).abs() < 1e-15);
640 }
641
642 #[test]
643 fn test_brush_falloff_smooth() {
644 assert!((BrushFalloff::Smooth.evaluate(0.0) - 1.0).abs() < 1e-15);
645 assert!((BrushFalloff::Smooth.evaluate(1.0)).abs() < 1e-15);
646 assert!((BrushFalloff::Smooth.evaluate(0.5) - 0.5).abs() < 1e-15);
648 }
649
650 #[test]
651 fn test_brush_falloff_sharp() {
652 assert!((BrushFalloff::Sharp.evaluate(0.0) - 1.0).abs() < 1e-15);
653 assert!((BrushFalloff::Sharp.evaluate(1.0)).abs() < 1e-15);
654 assert!((BrushFalloff::Sharp.evaluate(0.5) - 0.125).abs() < 1e-15);
656 }
657
658 #[test]
659 fn test_brush_falloff_flat() {
660 assert!((BrushFalloff::Flat.evaluate(0.0) - 1.0).abs() < 1e-15);
661 assert!((BrushFalloff::Flat.evaluate(0.5) - 1.0).abs() < 1e-15);
662 assert!((BrushFalloff::Flat.evaluate(1.0) - 1.0).abs() < 1e-15);
663 }
664
665 #[test]
666 fn test_set_mask_out_of_range() {
667 let mut p = DeltaPainter::new(3);
668 assert!(p.set_mask(5, 0.5).is_err());
669 }
670
671 #[test]
672 fn test_smooth_adjacency_too_short() {
673 let mut p = DeltaPainter::new(5);
674 let adj = vec![vec![1], vec![0]]; assert!(p.smooth(1, &adj).is_err());
676 }
677
678 #[test]
679 fn test_mirror_tolerance_too_small() {
680 let positions = vec![[1.0, 0.0, 0.0], [-1.0, 0.0, 0.0]];
681 let mut p = DeltaPainter::new(2);
682 assert!(p.mirror(MirrorAxis::X, &positions, -0.1).is_err());
683 }
684
685 #[test]
686 fn test_add_delta() {
687 let mut p = DeltaPainter::new(2);
688 p.set_delta(0, [1.0, 2.0, 3.0]).expect("ok");
689 p.add_delta(0, [0.5, 0.5, 0.5]).expect("ok");
690 let d = p.raw_deltas()[0];
691 assert!((d[0] - 1.5).abs() < 1e-15);
692 assert!((d[1] - 2.5).abs() < 1e-15);
693 assert!((d[2] - 3.5).abs() < 1e-15);
694 }
695
696 #[test]
697 fn test_clear_mask() {
698 let mut p = DeltaPainter::new(3);
699 p.set_mask(0, 0.0).expect("ok");
700 p.set_mask(1, 0.5).expect("ok");
701 p.clear_mask();
702 for &m in p.mask() {
703 assert!((m - 1.0).abs() < 1e-15);
704 }
705 }
706}