1#![allow(clippy::unwrap_used, clippy::disallowed_methods)]
2use serde::{Deserialize, Serialize};
30use std::f64::consts::PI;
31
32#[derive(Debug, Clone, Copy, PartialEq, Default, Serialize, Deserialize)]
34pub struct Point2D {
35 pub x: f64,
36 pub y: f64,
37}
38
39impl Point2D {
40 #[must_use]
42 pub const fn new(x: f64, y: f64) -> Self {
43 Self { x, y }
44 }
45
46 pub const ORIGIN: Self = Self { x: 0.0, y: 0.0 };
48
49 #[must_use]
51 pub fn distance(&self, other: &Self) -> f64 {
52 let dx = self.x - other.x;
53 let dy = self.y - other.y;
54 dx.hypot(dy)
55 }
56
57 #[must_use]
59 pub fn lerp(&self, other: &Self, t: f64) -> Self {
60 Self {
61 x: (other.x - self.x).mul_add(t, self.x),
62 y: (other.y - self.y).mul_add(t, self.y),
63 }
64 }
65}
66
67impl std::ops::Add for Point2D {
68 type Output = Self;
69
70 fn add(self, rhs: Self) -> Self::Output {
71 Self {
72 x: self.x + rhs.x,
73 y: self.y + rhs.y,
74 }
75 }
76}
77
78impl std::ops::Sub for Point2D {
79 type Output = Self;
80
81 fn sub(self, rhs: Self) -> Self::Output {
82 Self {
83 x: self.x - rhs.x,
84 y: self.y - rhs.y,
85 }
86 }
87}
88
89impl std::ops::Mul<f64> for Point2D {
90 type Output = Self;
91
92 fn mul(self, rhs: f64) -> Self::Output {
93 Self {
94 x: self.x * rhs,
95 y: self.y * rhs,
96 }
97 }
98}
99
100pub trait Interpolator {
102 fn interpolate(&self, x: f64) -> f64;
104
105 fn sample(&self, start: f64, end: f64, num_points: usize) -> Vec<Point2D> {
107 if num_points < 2 {
108 return vec![];
109 }
110 let step = (end - start) / (num_points - 1) as f64;
111 (0..num_points)
112 .map(|i| {
113 let x = (i as f64).mul_add(step, start);
114 Point2D::new(x, self.interpolate(x))
115 })
116 .collect()
117 }
118}
119
120#[derive(Debug, Clone)]
122pub struct LinearInterpolator {
123 points: Vec<Point2D>,
124}
125
126impl LinearInterpolator {
127 #[must_use]
129 pub fn from_points(points: &[Point2D]) -> Self {
130 let mut sorted = points.to_vec();
131 sorted.sort_by(|a, b| a.x.partial_cmp(&b.x).unwrap_or(std::cmp::Ordering::Equal));
132 Self { points: sorted }
133 }
134
135 #[must_use]
137 pub fn from_xy(xs: &[f64], ys: &[f64]) -> Self {
138 let points: Vec<_> = xs
139 .iter()
140 .zip(ys.iter())
141 .map(|(&x, &y)| Point2D::new(x, y))
142 .collect();
143 Self::from_points(&points)
144 }
145
146 #[must_use]
148 pub fn points(&self) -> &[Point2D] {
149 &self.points
150 }
151
152 fn find_segment(&self, x: f64) -> Option<(usize, f64)> {
154 if self.points.len() < 2 {
155 return None;
156 }
157 for i in 0..self.points.len() - 1 {
158 let p1 = &self.points[i];
159 let p2 = &self.points[i + 1];
160 if x >= p1.x && x <= p2.x {
161 let t = if (p2.x - p1.x).abs() < 1e-10 {
162 0.0
163 } else {
164 (x - p1.x) / (p2.x - p1.x)
165 };
166 return Some((i, t));
167 }
168 }
169 if x < self.points[0].x {
171 Some((
172 0,
173 (x - self.points[0].x) / (self.points[1].x - self.points[0].x),
174 ))
175 } else {
176 let n = self.points.len();
177 Some((
178 n - 2,
179 (x - self.points[n - 2].x) / (self.points[n - 1].x - self.points[n - 2].x),
180 ))
181 }
182 }
183}
184
185impl Interpolator for LinearInterpolator {
186 fn interpolate(&self, x: f64) -> f64 {
187 if self.points.is_empty() {
188 return 0.0;
189 }
190 if self.points.len() == 1 {
191 return self.points[0].y;
192 }
193
194 if let Some((i, t)) = self.find_segment(x) {
195 let p1 = &self.points[i];
196 let p2 = &self.points[i + 1];
197 (p2.y - p1.y).mul_add(t, p1.y)
198 } else {
199 0.0
200 }
201 }
202}
203
204#[derive(Debug, Clone)]
206pub struct CubicSpline {
207 points: Vec<Point2D>,
208 y2: Vec<f64>,
210}
211
212impl CubicSpline {
213 #[must_use]
215 pub fn from_points(points: &[Point2D]) -> Self {
216 let mut sorted = points.to_vec();
217 sorted.sort_by(|a, b| a.x.partial_cmp(&b.x).unwrap_or(std::cmp::Ordering::Equal));
218
219 let n = sorted.len();
220 if n < 3 {
221 return Self {
222 points: sorted,
223 y2: vec![0.0; n],
224 };
225 }
226
227 let mut y2 = vec![0.0; n];
229 let mut u = vec![0.0; n];
230
231 for i in 1..n - 1 {
233 let h_prev = sorted[i].x - sorted[i - 1].x;
234 let h_next = sorted[i + 1].x - sorted[i].x;
235
236 if h_prev.abs() < 1e-10 || h_next.abs() < 1e-10 {
237 continue;
238 }
239
240 let sig = h_prev / (h_prev + h_next);
241 let p = sig.mul_add(y2[i - 1], 2.0);
242 y2[i] = (sig - 1.0) / p;
243 u[i] =
244 (sorted[i + 1].y - sorted[i].y) / h_next - (sorted[i].y - sorted[i - 1].y) / h_prev;
245 u[i] = sig.mul_add(-u[i - 1], 6.0 * u[i] / (h_prev + h_next)) / p;
246 }
247
248 for k in (0..n - 1).rev() {
250 y2[k] = y2[k].mul_add(y2[k + 1], u[k]);
251 }
252
253 Self { points: sorted, y2 }
254 }
255
256 #[must_use]
258 pub fn from_xy(xs: &[f64], ys: &[f64]) -> Self {
259 let points: Vec<_> = xs
260 .iter()
261 .zip(ys.iter())
262 .map(|(&x, &y)| Point2D::new(x, y))
263 .collect();
264 Self::from_points(&points)
265 }
266
267 #[must_use]
269 pub fn points(&self) -> &[Point2D] {
270 &self.points
271 }
272}
273
274impl Interpolator for CubicSpline {
275 fn interpolate(&self, x: f64) -> f64 {
276 let n = self.points.len();
277 if n == 0 {
278 return 0.0;
279 }
280 if n == 1 {
281 return self.points[0].y;
282 }
283 if n == 2 {
284 let t = (x - self.points[0].x) / (self.points[1].x - self.points[0].x);
286 return (self.points[1].y - self.points[0].y).mul_add(t, self.points[0].y);
287 }
288
289 let mut lo = 0;
291 let mut hi = n - 1;
292 while hi - lo > 1 {
293 let mid = (hi + lo) / 2;
294 if self.points[mid].x > x {
295 hi = mid;
296 } else {
297 lo = mid;
298 }
299 }
300
301 let h = self.points[hi].x - self.points[lo].x;
302 if h.abs() < 1e-10 {
303 return self.points[lo].y;
304 }
305
306 let a = (self.points[hi].x - x) / h;
307 let b = (x - self.points[lo].x) / h;
308
309 a.mul_add(self.points[lo].y, b * self.points[hi].y)
310 + (a * a)
311 .mul_add(a, -a)
312 .mul_add(self.y2[lo], (b * b).mul_add(b, -b) * self.y2[hi])
313 * h
314 * h
315 / 6.0
316 }
317}
318
319#[derive(Debug, Clone)]
321pub struct CatmullRom {
322 points: Vec<Point2D>,
323 tension: f64,
325}
326
327impl CatmullRom {
328 #[must_use]
330 pub fn from_points(points: &[Point2D]) -> Self {
331 Self::with_tension(points, 0.5)
332 }
333
334 #[must_use]
336 pub fn with_tension(points: &[Point2D], tension: f64) -> Self {
337 let mut sorted = points.to_vec();
338 sorted.sort_by(|a, b| a.x.partial_cmp(&b.x).unwrap_or(std::cmp::Ordering::Equal));
339 Self {
340 points: sorted,
341 tension: tension.clamp(0.0, 1.0),
342 }
343 }
344
345 #[must_use]
347 pub fn points(&self) -> &[Point2D] {
348 &self.points
349 }
350
351 #[must_use]
353 pub fn to_path(&self, segments_per_span: usize) -> Vec<Point2D> {
354 if self.points.len() < 2 {
355 return self.points.clone();
356 }
357 if self.points.len() == 2 {
358 return self.points.clone();
359 }
360
361 let mut path = Vec::new();
362 let n = self.points.len();
363
364 for i in 0..n - 1 {
365 let p0 = if i == 0 {
366 self.points[0]
367 } else {
368 self.points[i - 1]
369 };
370 let p1 = self.points[i];
371 let p2 = self.points[i + 1];
372 let p3 = if i + 2 < n {
373 self.points[i + 2]
374 } else {
375 self.points[n - 1]
376 };
377
378 for j in 0..segments_per_span {
379 let t = j as f64 / segments_per_span as f64;
380 let point = self.catmull_rom_point(p0, p1, p2, p3, t);
381 path.push(point);
382 }
383 }
384
385 path.push(self.points[n - 1]);
387 path
388 }
389
390 fn catmull_rom_point(
392 &self,
393 p0: Point2D,
394 p1: Point2D,
395 p2: Point2D,
396 p3: Point2D,
397 t: f64,
398 ) -> Point2D {
399 let t2 = t * t;
400 let t3 = t2 * t;
401
402 let tau = self.tension;
403
404 let c0 = tau.mul_add(-t, (-tau).mul_add(t3, 2.0 * tau * t2));
406 let c1 = (2.0 - tau).mul_add(t3, (tau - 3.0) * t2) + 1.0;
407 let c2 = tau.mul_add(t, (tau - 2.0).mul_add(t3, 2.0f64.mul_add(-tau, 3.0) * t2));
408 let c3 = tau.mul_add(t3, -(tau * t2));
409
410 Point2D::new(
411 c3.mul_add(p3.x, c0 * p0.x + c1 * p1.x + c2 * p2.x),
412 c3.mul_add(p3.y, c0 * p0.y + c1 * p1.y + c2 * p2.y),
413 )
414 }
415}
416
417impl Interpolator for CatmullRom {
418 fn interpolate(&self, x: f64) -> f64 {
419 if self.points.is_empty() {
421 return 0.0;
422 }
423 if self.points.len() == 1 {
424 return self.points[0].y;
425 }
426
427 let mut idx = 0;
429 for i in 0..self.points.len() - 1 {
430 if x >= self.points[i].x && x <= self.points[i + 1].x {
431 idx = i;
432 break;
433 }
434 if x < self.points[i].x {
435 idx = i.saturating_sub(1);
436 break;
437 }
438 idx = i;
439 }
440
441 let p1 = &self.points[idx];
442 let p2 = &self.points[(idx + 1).min(self.points.len() - 1)];
443
444 let t = if (p2.x - p1.x).abs() < 1e-10 {
445 0.0
446 } else {
447 ((x - p1.x) / (p2.x - p1.x)).clamp(0.0, 1.0)
448 };
449
450 let p0 = if idx == 0 { *p1 } else { self.points[idx - 1] };
451 let p3 = if idx + 2 < self.points.len() {
452 self.points[idx + 2]
453 } else {
454 *p2
455 };
456
457 self.catmull_rom_point(p0, *p1, *p2, p3, t).y
458 }
459}
460
461#[derive(Debug, Clone, Copy, PartialEq)]
463pub struct CubicBezier {
464 pub p0: Point2D,
466 pub p1: Point2D,
468 pub p2: Point2D,
470 pub p3: Point2D,
472}
473
474impl CubicBezier {
475 #[must_use]
477 pub const fn new(p0: Point2D, p1: Point2D, p2: Point2D, p3: Point2D) -> Self {
478 Self { p0, p1, p2, p3 }
479 }
480
481 #[must_use]
483 pub fn evaluate(&self, t: f64) -> Point2D {
484 let t = t.clamp(0.0, 1.0);
485 let mt = 1.0 - t;
486 let mt2 = mt * mt;
487 let mt3 = mt2 * mt;
488 let t2 = t * t;
489 let t3 = t2 * t;
490
491 Point2D::new(
492 (3.0 * mt * t2).mul_add(self.p2.x, mt3 * self.p0.x + 3.0 * mt2 * t * self.p1.x)
493 + t3 * self.p3.x,
494 (3.0 * mt * t2).mul_add(self.p2.y, mt3 * self.p0.y + 3.0 * mt2 * t * self.p1.y)
495 + t3 * self.p3.y,
496 )
497 }
498
499 #[must_use]
501 pub fn to_polyline(&self, segments: usize) -> Vec<Point2D> {
502 let segments = segments.max(1);
503 (0..=segments)
504 .map(|i| self.evaluate(i as f64 / segments as f64))
505 .collect()
506 }
507
508 #[must_use]
510 pub fn arc_length(&self, segments: usize) -> f64 {
511 let points = self.to_polyline(segments);
512 points.windows(2).map(|w| w[0].distance(&w[1])).sum()
513 }
514
515 #[must_use]
517 pub fn split(&self, t: f64) -> (Self, Self) {
518 let t = t.clamp(0.0, 1.0);
519
520 let p01 = self.p0.lerp(&self.p1, t);
522 let p12 = self.p1.lerp(&self.p2, t);
523 let p23 = self.p2.lerp(&self.p3, t);
524
525 let p012 = p01.lerp(&p12, t);
526 let p123 = p12.lerp(&p23, t);
527
528 let p0123 = p012.lerp(&p123, t);
529
530 let left = Self::new(self.p0, p01, p012, p0123);
531 let right = Self::new(p0123, p123, p23, self.p3);
532
533 (left, right)
534 }
535}
536
537#[derive(Debug, Clone)]
539pub struct HistogramBins {
540 pub edges: Vec<f64>,
542 pub counts: Vec<usize>,
544 pub densities: Vec<f64>,
546}
547
548impl HistogramBins {
549 #[must_use]
551 pub fn from_data(data: &[f64], num_bins: usize) -> Self {
552 if data.is_empty() || num_bins == 0 {
553 return Self {
554 edges: vec![],
555 counts: vec![],
556 densities: vec![],
557 };
558 }
559
560 let num_bins = num_bins.max(1);
561 let min = data.iter().copied().fold(f64::INFINITY, f64::min);
562 let max = data.iter().copied().fold(f64::NEG_INFINITY, f64::max);
563
564 Self::from_data_range(data, num_bins, min, max)
565 }
566
567 #[must_use]
569 pub fn from_data_range(data: &[f64], num_bins: usize, min: f64, max: f64) -> Self {
570 let num_bins = num_bins.max(1);
571 let range = (max - min).max(1e-10);
572 let bin_width = range / num_bins as f64;
573
574 let edges: Vec<f64> = (0..=num_bins)
576 .map(|i| (i as f64).mul_add(bin_width, min))
577 .collect();
578
579 let mut counts = vec![0usize; num_bins];
581 for &value in data {
582 let bin = ((value - min) / bin_width).floor() as usize;
583 let bin = bin.min(num_bins - 1); counts[bin] += 1;
585 }
586
587 let total = data.len() as f64;
589 let densities: Vec<f64> = counts
590 .iter()
591 .map(|&c| (c as f64) / (total * bin_width))
592 .collect();
593
594 Self {
595 edges,
596 counts,
597 densities,
598 }
599 }
600
601 #[must_use]
603 pub fn num_bins(&self) -> usize {
604 self.counts.len()
605 }
606
607 #[must_use]
609 pub fn bin_width(&self) -> f64 {
610 if self.edges.len() < 2 {
611 return 0.0;
612 }
613 self.edges[1] - self.edges[0]
614 }
615
616 #[must_use]
618 pub fn bin_center(&self, index: usize) -> Option<f64> {
619 if index >= self.counts.len() {
620 return None;
621 }
622 Some((self.edges[index] + self.edges[index + 1]) / 2.0)
623 }
624
625 #[must_use]
627 pub fn bin_range(&self, index: usize) -> Option<(f64, f64)> {
628 if index >= self.counts.len() {
629 return None;
630 }
631 Some((self.edges[index], self.edges[index + 1]))
632 }
633
634 #[must_use]
636 pub fn total_count(&self) -> usize {
637 self.counts.iter().sum()
638 }
639
640 #[must_use]
642 pub fn max_count(&self) -> usize {
643 self.counts.iter().copied().max().unwrap_or(0)
644 }
645}
646
647#[derive(Debug, Clone, Copy, PartialEq)]
649pub struct ArcGeometry {
650 pub center: Point2D,
652 pub radius: f64,
654 pub start_angle: f64,
656 pub end_angle: f64,
658}
659
660impl ArcGeometry {
661 #[must_use]
663 pub const fn new(center: Point2D, radius: f64, start_angle: f64, end_angle: f64) -> Self {
664 Self {
665 center,
666 radius,
667 start_angle,
668 end_angle,
669 }
670 }
671
672 #[must_use]
674 pub fn circle(center: Point2D, radius: f64) -> Self {
675 Self::new(center, radius, 0.0, 2.0 * PI)
676 }
677
678 #[must_use]
680 pub fn sweep(&self) -> f64 {
681 self.end_angle - self.start_angle
682 }
683
684 #[must_use]
686 pub fn point_at_angle(&self, angle: f64) -> Point2D {
687 Point2D::new(
688 self.radius.mul_add(angle.cos(), self.center.x),
689 self.radius.mul_add(angle.sin(), self.center.y),
690 )
691 }
692
693 #[must_use]
695 pub fn start_point(&self) -> Point2D {
696 self.point_at_angle(self.start_angle)
697 }
698
699 #[must_use]
701 pub fn end_point(&self) -> Point2D {
702 self.point_at_angle(self.end_angle)
703 }
704
705 #[must_use]
707 pub fn mid_point(&self) -> Point2D {
708 let mid_angle = (self.start_angle + self.end_angle) / 2.0;
709 self.point_at_angle(mid_angle)
710 }
711
712 #[must_use]
714 pub fn arc_length(&self) -> f64 {
715 self.radius * self.sweep().abs()
716 }
717
718 #[must_use]
720 pub fn to_polyline(&self, segments: usize) -> Vec<Point2D> {
721 let segments = segments.max(1);
722 let sweep = self.sweep();
723 (0..=segments)
724 .map(|i| {
725 let t = i as f64 / segments as f64;
726 let angle = self.start_angle + t * sweep;
727 self.point_at_angle(angle)
728 })
729 .collect()
730 }
731
732 #[must_use]
734 pub fn to_pie_slice(&self, segments: usize) -> Vec<Point2D> {
735 let mut points = vec![self.center];
736 points.extend(self.to_polyline(segments));
737 points.push(self.center);
738 points
739 }
740
741 #[must_use]
743 pub fn contains_angle(&self, angle: f64) -> bool {
744 let normalized = Self::normalize_angle(angle);
745 let start = Self::normalize_angle(self.start_angle);
746 let end = Self::normalize_angle(self.end_angle);
747
748 if start <= end {
749 normalized >= start && normalized <= end
750 } else {
751 normalized >= start || normalized <= end
752 }
753 }
754
755 fn normalize_angle(angle: f64) -> f64 {
757 let mut a = angle % (2.0 * PI);
758 if a < 0.0 {
759 a += 2.0 * PI;
760 }
761 a
762 }
763}
764
765#[derive(Debug, Clone, Copy)]
767pub struct DataNormalizer {
768 pub min: f64,
770 pub max: f64,
772}
773
774impl DataNormalizer {
775 #[must_use]
777 pub fn new(min: f64, max: f64) -> Self {
778 Self { min, max }
779 }
780
781 #[must_use]
783 pub fn from_data(data: &[f64]) -> Self {
784 if data.is_empty() {
785 return Self::new(0.0, 1.0);
786 }
787 let min = data.iter().copied().fold(f64::INFINITY, f64::min);
788 let max = data.iter().copied().fold(f64::NEG_INFINITY, f64::max);
789 Self::new(min, max)
790 }
791
792 #[must_use]
794 pub fn normalize(&self, value: f64) -> f64 {
795 let range = self.max - self.min;
796 if range.abs() < 1e-10 {
797 return 0.5;
798 }
799 (value - self.min) / range
800 }
801
802 #[must_use]
804 pub fn denormalize(&self, normalized: f64) -> f64 {
805 normalized.mul_add(self.max - self.min, self.min)
806 }
807
808 #[must_use]
810 pub fn normalize_all(&self, data: &[f64]) -> Vec<f64> {
811 data.iter().map(|&v| self.normalize(v)).collect()
812 }
813
814 #[must_use]
816 pub fn nice_bounds(&self) -> (f64, f64) {
817 let range = self.max - self.min;
818 if range.abs() < 1e-10 {
819 return (self.min - 1.0, self.max + 1.0);
820 }
821
822 let magnitude = 10.0_f64.powf(range.log10().floor());
823 let nice_min = (self.min / magnitude).floor() * magnitude;
824 let nice_max = (self.max / magnitude).ceil() * magnitude;
825
826 (nice_min, nice_max)
827 }
828}
829
830#[derive(Debug, Clone, Default)]
832pub struct PathTessellator {
833 pub tolerance: f64,
835 pub vertices: Vec<[f32; 2]>,
837 pub indices: Vec<u32>,
839}
840
841impl PathTessellator {
842 #[must_use]
844 pub fn new(tolerance: f64) -> Self {
845 Self {
846 tolerance: tolerance.max(0.001),
847 vertices: Vec::new(),
848 indices: Vec::new(),
849 }
850 }
851
852 #[must_use]
854 pub fn with_default_tolerance() -> Self {
855 Self::new(0.25)
856 }
857
858 pub fn clear(&mut self) {
860 self.vertices.clear();
861 self.indices.clear();
862 }
863
864 pub fn tessellate_polygon(&mut self, points: &[Point2D]) {
866 if points.len() < 3 {
867 return;
868 }
869
870 let base_idx = self.vertices.len() as u32;
871
872 for p in points {
874 self.vertices.push([p.x as f32, p.y as f32]);
875 }
876
877 for i in 1..points.len() as u32 - 1 {
879 self.indices.push(base_idx);
880 self.indices.push(base_idx + i);
881 self.indices.push(base_idx + i + 1);
882 }
883 }
884
885 pub fn tessellate_stroke(&mut self, points: &[Point2D], width: f64) {
887 if points.len() < 2 {
888 return;
889 }
890
891 let half_width = width / 2.0;
892
893 for window in points.windows(2) {
894 let p1 = window[0];
895 let p2 = window[1];
896
897 let dx = p2.x - p1.x;
899 let dy = p2.y - p1.y;
900 let len = dx.hypot(dy);
901 if len < 1e-10 {
902 continue;
903 }
904
905 let nx = -dy / len * half_width;
906 let ny = dx / len * half_width;
907
908 let base_idx = self.vertices.len() as u32;
909
910 self.vertices.push([(p1.x + nx) as f32, (p1.y + ny) as f32]);
912 self.vertices.push([(p1.x - nx) as f32, (p1.y - ny) as f32]);
913 self.vertices.push([(p2.x + nx) as f32, (p2.y + ny) as f32]);
914 self.vertices.push([(p2.x - nx) as f32, (p2.y - ny) as f32]);
915
916 self.indices.push(base_idx);
918 self.indices.push(base_idx + 1);
919 self.indices.push(base_idx + 2);
920
921 self.indices.push(base_idx + 1);
922 self.indices.push(base_idx + 3);
923 self.indices.push(base_idx + 2);
924 }
925 }
926
927 pub fn tessellate_circle(&mut self, center: Point2D, radius: f64, segments: usize) {
929 let segments = segments.max(8);
930 let base_idx = self.vertices.len() as u32;
931
932 self.vertices.push([center.x as f32, center.y as f32]);
934
935 for i in 0..segments {
937 let angle = 2.0 * PI * i as f64 / segments as f64;
938 let x = radius.mul_add(angle.cos(), center.x);
939 let y = radius.mul_add(angle.sin(), center.y);
940 self.vertices.push([x as f32, y as f32]);
941 }
942
943 for i in 0..segments as u32 {
945 self.indices.push(base_idx); self.indices.push(base_idx + 1 + i);
947 self.indices.push(base_idx + 1 + (i + 1) % segments as u32);
948 }
949 }
950
951 pub fn tessellate_rect(&mut self, x: f64, y: f64, width: f64, height: f64) {
953 let base_idx = self.vertices.len() as u32;
954
955 self.vertices.push([x as f32, y as f32]);
956 self.vertices.push([(x + width) as f32, y as f32]);
957 self.vertices
958 .push([(x + width) as f32, (y + height) as f32]);
959 self.vertices.push([x as f32, (y + height) as f32]);
960
961 self.indices.push(base_idx);
963 self.indices.push(base_idx + 1);
964 self.indices.push(base_idx + 2);
965
966 self.indices.push(base_idx);
967 self.indices.push(base_idx + 2);
968 self.indices.push(base_idx + 3);
969 }
970
971 #[must_use]
973 pub fn vertex_count(&self) -> usize {
974 self.vertices.len()
975 }
976
977 #[must_use]
979 pub fn index_count(&self) -> usize {
980 self.indices.len()
981 }
982
983 #[must_use]
985 pub fn triangle_count(&self) -> usize {
986 self.indices.len() / 3
987 }
988}
989
990#[derive(Debug, Clone, Default)]
992pub struct DrawBatch {
993 pub circles: Vec<[f32; 7]>,
995 pub rects: Vec<[f32; 8]>,
997 pub lines: Vec<[f32; 9]>,
999}
1000
1001impl DrawBatch {
1002 #[must_use]
1004 pub fn new() -> Self {
1005 Self::default()
1006 }
1007
1008 pub fn add_circle(&mut self, x: f32, y: f32, radius: f32, r: f32, g: f32, b: f32, a: f32) {
1010 self.circles.push([x, y, radius, r, g, b, a]);
1011 }
1012
1013 #[allow(clippy::too_many_arguments)]
1015 pub fn add_rect(&mut self, x: f32, y: f32, w: f32, h: f32, r: f32, g: f32, b: f32, a: f32) {
1016 self.rects.push([x, y, w, h, r, g, b, a]);
1017 }
1018
1019 #[allow(clippy::too_many_arguments)]
1021 pub fn add_line(
1022 &mut self,
1023 x1: f32,
1024 y1: f32,
1025 x2: f32,
1026 y2: f32,
1027 width: f32,
1028 r: f32,
1029 g: f32,
1030 b: f32,
1031 a: f32,
1032 ) {
1033 self.lines.push([x1, y1, x2, y2, width, r, g, b, a]);
1034 }
1035
1036 pub fn clear(&mut self) {
1038 self.circles.clear();
1039 self.rects.clear();
1040 self.lines.clear();
1041 }
1042
1043 #[must_use]
1045 pub fn unbatched_draw_calls(&self) -> usize {
1046 self.circles.len() + self.rects.len() + self.lines.len()
1047 }
1048
1049 #[must_use]
1051 pub fn batched_draw_calls(&self) -> usize {
1052 let mut calls = 0;
1053 if !self.circles.is_empty() {
1054 calls += 1;
1055 }
1056 if !self.rects.is_empty() {
1057 calls += 1;
1058 }
1059 if !self.lines.is_empty() {
1060 calls += 1;
1061 }
1062 calls
1063 }
1064}
1065
1066#[cfg(test)]
1067mod tests {
1068 use super::*;
1069
1070 #[test]
1075 fn test_point2d_new() {
1076 let p = Point2D::new(1.0, 2.0);
1077 assert_eq!(p.x, 1.0);
1078 assert_eq!(p.y, 2.0);
1079 }
1080
1081 #[test]
1082 fn test_point2d_origin() {
1083 assert_eq!(Point2D::ORIGIN, Point2D::new(0.0, 0.0));
1084 }
1085
1086 #[test]
1087 fn test_point2d_distance() {
1088 let p1 = Point2D::new(0.0, 0.0);
1089 let p2 = Point2D::new(3.0, 4.0);
1090 assert!((p1.distance(&p2) - 5.0).abs() < 1e-10);
1091 }
1092
1093 #[test]
1094 fn test_point2d_lerp() {
1095 let p1 = Point2D::new(0.0, 0.0);
1096 let p2 = Point2D::new(10.0, 20.0);
1097 let mid = p1.lerp(&p2, 0.5);
1098 assert!((mid.x - 5.0).abs() < 1e-10);
1099 assert!((mid.y - 10.0).abs() < 1e-10);
1100 }
1101
1102 #[test]
1103 fn test_point2d_add() {
1104 let p1 = Point2D::new(1.0, 2.0);
1105 let p2 = Point2D::new(3.0, 4.0);
1106 let sum = p1 + p2;
1107 assert_eq!(sum, Point2D::new(4.0, 6.0));
1108 }
1109
1110 #[test]
1111 fn test_point2d_sub() {
1112 let p1 = Point2D::new(5.0, 7.0);
1113 let p2 = Point2D::new(2.0, 3.0);
1114 let diff = p1 - p2;
1115 assert_eq!(diff, Point2D::new(3.0, 4.0));
1116 }
1117
1118 #[test]
1119 fn test_point2d_mul() {
1120 let p = Point2D::new(2.0, 3.0);
1121 let scaled = p * 2.0;
1122 assert_eq!(scaled, Point2D::new(4.0, 6.0));
1123 }
1124
1125 #[test]
1130 fn test_linear_empty() {
1131 let interp = LinearInterpolator::from_points(&[]);
1132 assert_eq!(interp.interpolate(0.0), 0.0);
1133 }
1134
1135 #[test]
1136 fn test_linear_single_point() {
1137 let interp = LinearInterpolator::from_points(&[Point2D::new(1.0, 5.0)]);
1138 assert_eq!(interp.interpolate(0.0), 5.0);
1139 assert_eq!(interp.interpolate(2.0), 5.0);
1140 }
1141
1142 #[test]
1143 fn test_linear_two_points() {
1144 let interp =
1145 LinearInterpolator::from_points(&[Point2D::new(0.0, 0.0), Point2D::new(10.0, 20.0)]);
1146 assert!((interp.interpolate(0.0) - 0.0).abs() < 1e-10);
1147 assert!((interp.interpolate(5.0) - 10.0).abs() < 1e-10);
1148 assert!((interp.interpolate(10.0) - 20.0).abs() < 1e-10);
1149 }
1150
1151 #[test]
1152 fn test_linear_multiple_points() {
1153 let interp = LinearInterpolator::from_points(&[
1154 Point2D::new(0.0, 0.0),
1155 Point2D::new(1.0, 2.0),
1156 Point2D::new(2.0, 1.0),
1157 Point2D::new(3.0, 3.0),
1158 ]);
1159
1160 assert!((interp.interpolate(0.0) - 0.0).abs() < 1e-10);
1162 assert!((interp.interpolate(1.0) - 2.0).abs() < 1e-10);
1163 assert!((interp.interpolate(2.0) - 1.0).abs() < 1e-10);
1164 assert!((interp.interpolate(3.0) - 3.0).abs() < 1e-10);
1165
1166 assert!((interp.interpolate(0.5) - 1.0).abs() < 1e-10);
1168 assert!((interp.interpolate(1.5) - 1.5).abs() < 1e-10);
1169 }
1170
1171 #[test]
1172 fn test_linear_from_xy() {
1173 let xs = [0.0, 1.0, 2.0];
1174 let ys = [0.0, 10.0, 20.0];
1175 let interp = LinearInterpolator::from_xy(&xs, &ys);
1176 assert!((interp.interpolate(1.5) - 15.0).abs() < 1e-10);
1177 }
1178
1179 #[test]
1180 fn test_linear_sample() {
1181 let interp =
1182 LinearInterpolator::from_points(&[Point2D::new(0.0, 0.0), Point2D::new(10.0, 10.0)]);
1183 let samples = interp.sample(0.0, 10.0, 11);
1184 assert_eq!(samples.len(), 11);
1185 assert!((samples[0].x - 0.0).abs() < 1e-10);
1186 assert!((samples[10].x - 10.0).abs() < 1e-10);
1187 }
1188
1189 #[test]
1194 fn test_spline_empty() {
1195 let spline = CubicSpline::from_points(&[]);
1196 assert_eq!(spline.interpolate(0.0), 0.0);
1197 }
1198
1199 #[test]
1200 fn test_spline_single_point() {
1201 let spline = CubicSpline::from_points(&[Point2D::new(1.0, 5.0)]);
1202 assert_eq!(spline.interpolate(0.0), 5.0);
1203 }
1204
1205 #[test]
1206 fn test_spline_two_points() {
1207 let spline = CubicSpline::from_points(&[Point2D::new(0.0, 0.0), Point2D::new(10.0, 20.0)]);
1208 assert!((spline.interpolate(5.0) - 10.0).abs() < 1e-10);
1209 }
1210
1211 #[test]
1212 fn test_spline_passes_through_points() {
1213 let points = vec![
1214 Point2D::new(0.0, 0.0),
1215 Point2D::new(1.0, 2.0),
1216 Point2D::new(2.0, 1.5),
1217 Point2D::new(3.0, 3.0),
1218 ];
1219 let spline = CubicSpline::from_points(&points);
1220
1221 for p in &points {
1222 assert!(
1223 (spline.interpolate(p.x) - p.y).abs() < 0.01,
1224 "Spline should pass through control points"
1225 );
1226 }
1227 }
1228
1229 #[test]
1230 fn test_spline_smooth() {
1231 let points = vec![
1232 Point2D::new(0.0, 0.0),
1233 Point2D::new(1.0, 1.0),
1234 Point2D::new(2.0, 0.0),
1235 ];
1236 let spline = CubicSpline::from_points(&points);
1237
1238 let samples = spline.sample(0.0, 2.0, 100);
1240 for w in samples.windows(3) {
1241 let dy1 = (w[1].y - w[0].y).abs();
1243 let dy2 = (w[2].y - w[1].y).abs();
1244 assert!(dy1 < 0.5 && dy2 < 0.5, "Spline should be smooth");
1245 }
1246 }
1247
1248 #[test]
1253 fn test_catmull_rom_empty() {
1254 let cr = CatmullRom::from_points(&[]);
1255 assert_eq!(cr.interpolate(0.0), 0.0);
1256 }
1257
1258 #[test]
1259 fn test_catmull_rom_single() {
1260 let cr = CatmullRom::from_points(&[Point2D::new(1.0, 5.0)]);
1261 assert_eq!(cr.interpolate(0.0), 5.0);
1262 }
1263
1264 #[test]
1265 fn test_catmull_rom_passes_through() {
1266 let points = vec![
1267 Point2D::new(0.0, 0.0),
1268 Point2D::new(1.0, 2.0),
1269 Point2D::new(2.0, 1.0),
1270 Point2D::new(3.0, 3.0),
1271 ];
1272 let cr = CatmullRom::from_points(&points);
1273
1274 for p in &points {
1276 let y = cr.interpolate(p.x);
1277 assert!(
1278 (y - p.y).abs() < 0.1,
1279 "Catmull-Rom should pass through points: expected {} at x={}, got {}",
1280 p.y,
1281 p.x,
1282 y
1283 );
1284 }
1285 }
1286
1287 #[test]
1288 fn test_catmull_rom_to_path() {
1289 let points = vec![
1290 Point2D::new(0.0, 0.0),
1291 Point2D::new(1.0, 1.0),
1292 Point2D::new(2.0, 0.0),
1293 ];
1294 let cr = CatmullRom::from_points(&points);
1295 let path = cr.to_path(10);
1296
1297 assert!(path.len() > points.len());
1298 assert_eq!(path.first().unwrap().x, 0.0);
1299 assert_eq!(path.last().unwrap().x, 2.0);
1300 }
1301
1302 #[test]
1303 fn test_catmull_rom_tension() {
1304 let points = vec![
1305 Point2D::new(0.0, 0.0),
1306 Point2D::new(1.0, 1.0),
1307 Point2D::new(2.0, 0.0),
1308 ];
1309
1310 let low_tension = CatmullRom::with_tension(&points, 0.0);
1311 let high_tension = CatmullRom::with_tension(&points, 1.0);
1312
1313 let y_low = low_tension.interpolate(0.5);
1315 let y_high = high_tension.interpolate(0.5);
1316
1317 assert!((y_low - y_high).abs() > 0.01 || (y_low - y_high).abs() < 0.5);
1319 }
1320
1321 #[test]
1326 fn test_bezier_endpoints() {
1327 let bezier = CubicBezier::new(
1328 Point2D::new(0.0, 0.0),
1329 Point2D::new(1.0, 2.0),
1330 Point2D::new(2.0, 2.0),
1331 Point2D::new(3.0, 0.0),
1332 );
1333
1334 let start = bezier.evaluate(0.0);
1335 let end = bezier.evaluate(1.0);
1336
1337 assert!((start.x - 0.0).abs() < 1e-10);
1338 assert!((start.y - 0.0).abs() < 1e-10);
1339 assert!((end.x - 3.0).abs() < 1e-10);
1340 assert!((end.y - 0.0).abs() < 1e-10);
1341 }
1342
1343 #[test]
1344 fn test_bezier_midpoint() {
1345 let bezier = CubicBezier::new(
1346 Point2D::new(0.0, 0.0),
1347 Point2D::new(0.0, 1.0),
1348 Point2D::new(1.0, 1.0),
1349 Point2D::new(1.0, 0.0),
1350 );
1351
1352 let mid = bezier.evaluate(0.5);
1353 assert!(mid.x > 0.0 && mid.x < 1.0);
1355 assert!(mid.y > 0.0 && mid.y < 1.0);
1356 }
1357
1358 #[test]
1359 fn test_bezier_to_polyline() {
1360 let bezier = CubicBezier::new(
1361 Point2D::new(0.0, 0.0),
1362 Point2D::new(1.0, 2.0),
1363 Point2D::new(2.0, 2.0),
1364 Point2D::new(3.0, 0.0),
1365 );
1366
1367 let polyline = bezier.to_polyline(10);
1368 assert_eq!(polyline.len(), 11);
1369 assert_eq!(polyline[0], bezier.evaluate(0.0));
1370 assert_eq!(polyline[10], bezier.evaluate(1.0));
1371 }
1372
1373 #[test]
1374 fn test_bezier_arc_length() {
1375 let line = CubicBezier::new(
1376 Point2D::new(0.0, 0.0),
1377 Point2D::new(1.0, 0.0),
1378 Point2D::new(2.0, 0.0),
1379 Point2D::new(3.0, 0.0),
1380 );
1381
1382 let length = line.arc_length(100);
1383 assert!((length - 3.0).abs() < 0.01);
1384 }
1385
1386 #[test]
1387 fn test_bezier_split() {
1388 let bezier = CubicBezier::new(
1389 Point2D::new(0.0, 0.0),
1390 Point2D::new(1.0, 2.0),
1391 Point2D::new(2.0, 2.0),
1392 Point2D::new(3.0, 0.0),
1393 );
1394
1395 let (left, right) = bezier.split(0.5);
1396
1397 assert_eq!(left.p0, bezier.p0);
1399
1400 assert_eq!(right.p3, bezier.p3);
1402
1403 assert!((left.p3.x - right.p0.x).abs() < 1e-10);
1405 assert!((left.p3.y - right.p0.y).abs() < 1e-10);
1406 }
1407
1408 #[test]
1413 fn test_histogram_empty() {
1414 let hist = HistogramBins::from_data(&[], 10);
1415 assert_eq!(hist.num_bins(), 0);
1416 }
1417
1418 #[test]
1419 fn test_histogram_single_value() {
1420 let hist = HistogramBins::from_data(&[5.0], 10);
1421 assert_eq!(hist.total_count(), 1);
1422 }
1423
1424 #[test]
1425 fn test_histogram_uniform() {
1426 let data: Vec<f64> = (0..100).map(|i| i as f64).collect();
1427 let hist = HistogramBins::from_data(&data, 10);
1428
1429 assert_eq!(hist.num_bins(), 10);
1430 assert_eq!(hist.total_count(), 100);
1431
1432 for &count in &hist.counts {
1434 assert!(count >= 9 && count <= 11);
1435 }
1436 }
1437
1438 #[test]
1439 fn test_histogram_bin_width() {
1440 let hist = HistogramBins::from_data_range(&[0.0], 5, 0.0, 10.0);
1441 assert!((hist.bin_width() - 2.0).abs() < 1e-10);
1442 }
1443
1444 #[test]
1445 fn test_histogram_bin_center() {
1446 let hist = HistogramBins::from_data_range(&[0.0], 4, 0.0, 8.0);
1447 assert_eq!(hist.bin_center(0), Some(1.0));
1448 assert_eq!(hist.bin_center(1), Some(3.0));
1449 assert_eq!(hist.bin_center(4), None);
1450 }
1451
1452 #[test]
1453 fn test_histogram_bin_range() {
1454 let hist = HistogramBins::from_data_range(&[0.0], 4, 0.0, 8.0);
1455 assert_eq!(hist.bin_range(0), Some((0.0, 2.0)));
1456 assert_eq!(hist.bin_range(3), Some((6.0, 8.0)));
1457 }
1458
1459 #[test]
1460 fn test_histogram_densities() {
1461 let data = vec![0.5, 1.5, 1.5, 2.5, 2.5, 2.5];
1462 let hist = HistogramBins::from_data_range(&data, 3, 0.0, 3.0);
1463
1464 let total_density: f64 = hist.densities.iter().map(|d| d * hist.bin_width()).sum();
1466 assert!((total_density - 1.0).abs() < 1e-10);
1467 }
1468
1469 #[test]
1470 fn test_histogram_max_count() {
1471 let data = vec![1.0, 1.0, 1.0, 2.0];
1472 let hist = HistogramBins::from_data_range(&data, 2, 0.0, 4.0);
1473 assert_eq!(hist.max_count(), 3);
1474 }
1475
1476 #[test]
1481 fn test_arc_new() {
1482 let arc = ArcGeometry::new(Point2D::new(0.0, 0.0), 10.0, 0.0, PI);
1483 assert_eq!(arc.center, Point2D::ORIGIN);
1484 assert_eq!(arc.radius, 10.0);
1485 }
1486
1487 #[test]
1488 fn test_arc_circle() {
1489 let circle = ArcGeometry::circle(Point2D::new(5.0, 5.0), 3.0);
1490 assert!((circle.sweep() - 2.0 * PI).abs() < 1e-10);
1491 }
1492
1493 #[test]
1494 fn test_arc_sweep() {
1495 let arc = ArcGeometry::new(Point2D::ORIGIN, 1.0, 0.0, PI / 2.0);
1496 assert!((arc.sweep() - PI / 2.0).abs() < 1e-10);
1497 }
1498
1499 #[test]
1500 fn test_arc_point_at_angle() {
1501 let arc = ArcGeometry::circle(Point2D::ORIGIN, 1.0);
1502
1503 let p0 = arc.point_at_angle(0.0);
1504 assert!((p0.x - 1.0).abs() < 1e-10);
1505 assert!((p0.y - 0.0).abs() < 1e-10);
1506
1507 let p90 = arc.point_at_angle(PI / 2.0);
1508 assert!((p90.x - 0.0).abs() < 1e-10);
1509 assert!((p90.y - 1.0).abs() < 1e-10);
1510 }
1511
1512 #[test]
1513 fn test_arc_start_end_points() {
1514 let arc = ArcGeometry::new(Point2D::ORIGIN, 1.0, 0.0, PI);
1515
1516 let start = arc.start_point();
1517 assert!((start.x - 1.0).abs() < 1e-10);
1518
1519 let end = arc.end_point();
1520 assert!((end.x - (-1.0)).abs() < 1e-10);
1521 }
1522
1523 #[test]
1524 fn test_arc_mid_point() {
1525 let arc = ArcGeometry::new(Point2D::ORIGIN, 1.0, 0.0, PI);
1526 let mid = arc.mid_point();
1527 assert!((mid.y - 1.0).abs() < 1e-10);
1528 }
1529
1530 #[test]
1531 fn test_arc_length() {
1532 let semicircle = ArcGeometry::new(Point2D::ORIGIN, 1.0, 0.0, PI);
1533 assert!((semicircle.arc_length() - PI).abs() < 1e-10);
1534
1535 let full = ArcGeometry::circle(Point2D::ORIGIN, 1.0);
1536 assert!((full.arc_length() - 2.0 * PI).abs() < 1e-10);
1537 }
1538
1539 #[test]
1540 fn test_arc_to_polyline() {
1541 let arc = ArcGeometry::new(Point2D::ORIGIN, 1.0, 0.0, PI);
1542 let poly = arc.to_polyline(4);
1543
1544 assert_eq!(poly.len(), 5);
1545 assert!((poly[0].x - 1.0).abs() < 1e-10); assert!((poly[4].x - (-1.0)).abs() < 1e-10); }
1548
1549 #[test]
1550 fn test_arc_to_pie_slice() {
1551 let arc = ArcGeometry::new(Point2D::ORIGIN, 1.0, 0.0, PI / 2.0);
1552 let slice = arc.to_pie_slice(4);
1553
1554 assert_eq!(slice[0], Point2D::ORIGIN);
1556 assert_eq!(slice[slice.len() - 1], Point2D::ORIGIN);
1557 }
1558
1559 #[test]
1560 fn test_arc_contains_angle() {
1561 let arc = ArcGeometry::new(Point2D::ORIGIN, 1.0, 0.0, PI);
1562 assert!(arc.contains_angle(PI / 2.0));
1563 assert!(arc.contains_angle(0.0));
1564 assert!(!arc.contains_angle(3.0 * PI / 2.0));
1565 }
1566
1567 #[test]
1572 fn test_normalizer_new() {
1573 let norm = DataNormalizer::new(0.0, 100.0);
1574 assert_eq!(norm.min, 0.0);
1575 assert_eq!(norm.max, 100.0);
1576 }
1577
1578 #[test]
1579 fn test_normalizer_from_data() {
1580 let data = vec![10.0, 20.0, 30.0, 40.0, 50.0];
1581 let norm = DataNormalizer::from_data(&data);
1582 assert_eq!(norm.min, 10.0);
1583 assert_eq!(norm.max, 50.0);
1584 }
1585
1586 #[test]
1587 fn test_normalizer_from_empty() {
1588 let norm = DataNormalizer::from_data(&[]);
1589 assert_eq!(norm.min, 0.0);
1590 assert_eq!(norm.max, 1.0);
1591 }
1592
1593 #[test]
1594 fn test_normalizer_normalize() {
1595 let norm = DataNormalizer::new(0.0, 100.0);
1596 assert!((norm.normalize(0.0) - 0.0).abs() < 1e-10);
1597 assert!((norm.normalize(50.0) - 0.5).abs() < 1e-10);
1598 assert!((norm.normalize(100.0) - 1.0).abs() < 1e-10);
1599 }
1600
1601 #[test]
1602 fn test_normalizer_denormalize() {
1603 let norm = DataNormalizer::new(0.0, 100.0);
1604 assert!((norm.denormalize(0.0) - 0.0).abs() < 1e-10);
1605 assert!((norm.denormalize(0.5) - 50.0).abs() < 1e-10);
1606 assert!((norm.denormalize(1.0) - 100.0).abs() < 1e-10);
1607 }
1608
1609 #[test]
1610 fn test_normalizer_roundtrip() {
1611 let norm = DataNormalizer::new(-50.0, 150.0);
1612 let values = vec![-50.0, 0.0, 50.0, 100.0, 150.0];
1613
1614 for &v in &values {
1615 let normalized = norm.normalize(v);
1616 let denormalized = norm.denormalize(normalized);
1617 assert!((v - denormalized).abs() < 1e-10);
1618 }
1619 }
1620
1621 #[test]
1622 fn test_normalizer_normalize_all() {
1623 let norm = DataNormalizer::new(0.0, 10.0);
1624 let data = vec![0.0, 5.0, 10.0];
1625 let normalized = norm.normalize_all(&data);
1626
1627 assert_eq!(normalized, vec![0.0, 0.5, 1.0]);
1628 }
1629
1630 #[test]
1631 fn test_normalizer_nice_bounds() {
1632 let norm = DataNormalizer::new(3.2, 97.8);
1633 let (nice_min, nice_max) = norm.nice_bounds();
1634
1635 assert!(nice_min <= 3.2);
1636 assert!(nice_max >= 97.8);
1637 assert!((nice_min * 10.0).round() == nice_min * 10.0);
1639 }
1640
1641 #[test]
1646 fn test_tessellator_new() {
1647 let tess = PathTessellator::new(0.5);
1648 assert!((tess.tolerance - 0.5).abs() < 1e-10);
1649 assert!(tess.vertices.is_empty());
1650 assert!(tess.indices.is_empty());
1651 }
1652
1653 #[test]
1654 fn test_tessellator_default() {
1655 let tess = PathTessellator::with_default_tolerance();
1656 assert!((tess.tolerance - 0.25).abs() < 1e-10);
1657 }
1658
1659 #[test]
1660 fn test_tessellator_polygon() {
1661 let mut tess = PathTessellator::new(0.5);
1662 let triangle = vec![
1663 Point2D::new(0.0, 0.0),
1664 Point2D::new(1.0, 0.0),
1665 Point2D::new(0.5, 1.0),
1666 ];
1667
1668 tess.tessellate_polygon(&triangle);
1669
1670 assert_eq!(tess.vertex_count(), 3);
1671 assert_eq!(tess.index_count(), 3);
1672 assert_eq!(tess.triangle_count(), 1);
1673 }
1674
1675 #[test]
1676 fn test_tessellator_quad() {
1677 let mut tess = PathTessellator::new(0.5);
1678 let quad = vec![
1679 Point2D::new(0.0, 0.0),
1680 Point2D::new(1.0, 0.0),
1681 Point2D::new(1.0, 1.0),
1682 Point2D::new(0.0, 1.0),
1683 ];
1684
1685 tess.tessellate_polygon(&quad);
1686
1687 assert_eq!(tess.vertex_count(), 4);
1688 assert_eq!(tess.triangle_count(), 2);
1689 }
1690
1691 #[test]
1692 fn test_tessellator_stroke() {
1693 let mut tess = PathTessellator::new(0.5);
1694 let line = vec![Point2D::new(0.0, 0.0), Point2D::new(10.0, 0.0)];
1695
1696 tess.tessellate_stroke(&line, 2.0);
1697
1698 assert_eq!(tess.vertex_count(), 4);
1700 assert_eq!(tess.triangle_count(), 2);
1701 }
1702
1703 #[test]
1704 fn test_tessellator_multi_segment_stroke() {
1705 let mut tess = PathTessellator::new(0.5);
1706 let path = vec![
1707 Point2D::new(0.0, 0.0),
1708 Point2D::new(10.0, 0.0),
1709 Point2D::new(10.0, 10.0),
1710 ];
1711
1712 tess.tessellate_stroke(&path, 1.0);
1713
1714 assert_eq!(tess.vertex_count(), 8);
1716 assert_eq!(tess.triangle_count(), 4);
1717 }
1718
1719 #[test]
1720 fn test_tessellator_circle() {
1721 let mut tess = PathTessellator::new(0.5);
1722 tess.tessellate_circle(Point2D::ORIGIN, 1.0, 16);
1723
1724 assert_eq!(tess.vertex_count(), 17);
1726 assert_eq!(tess.triangle_count(), 16);
1728 }
1729
1730 #[test]
1731 fn test_tessellator_rect() {
1732 let mut tess = PathTessellator::new(0.5);
1733 tess.tessellate_rect(0.0, 0.0, 10.0, 5.0);
1734
1735 assert_eq!(tess.vertex_count(), 4);
1736 assert_eq!(tess.triangle_count(), 2);
1737 }
1738
1739 #[test]
1740 fn test_tessellator_clear() {
1741 let mut tess = PathTessellator::new(0.5);
1742 tess.tessellate_rect(0.0, 0.0, 10.0, 5.0);
1743 assert!(!tess.vertices.is_empty());
1744
1745 tess.clear();
1746 assert!(tess.vertices.is_empty());
1747 assert!(tess.indices.is_empty());
1748 }
1749
1750 #[test]
1751 fn test_tessellator_multiple_shapes() {
1752 let mut tess = PathTessellator::new(0.5);
1753
1754 tess.tessellate_rect(0.0, 0.0, 10.0, 10.0);
1755 tess.tessellate_circle(Point2D::new(20.0, 5.0), 3.0, 8);
1756
1757 assert_eq!(tess.vertex_count(), 4 + 9); assert_eq!(tess.triangle_count(), 2 + 8);
1759 }
1760
1761 #[test]
1766 fn test_batch_new() {
1767 let batch = DrawBatch::new();
1768 assert!(batch.circles.is_empty());
1769 assert!(batch.rects.is_empty());
1770 assert!(batch.lines.is_empty());
1771 }
1772
1773 #[test]
1774 fn test_batch_add_circle() {
1775 let mut batch = DrawBatch::new();
1776 batch.add_circle(10.0, 20.0, 5.0, 1.0, 0.0, 0.0, 1.0);
1777
1778 assert_eq!(batch.circles.len(), 1);
1779 assert_eq!(batch.circles[0][0], 10.0);
1780 assert_eq!(batch.circles[0][1], 20.0);
1781 assert_eq!(batch.circles[0][2], 5.0);
1782 }
1783
1784 #[test]
1785 fn test_batch_add_rect() {
1786 let mut batch = DrawBatch::new();
1787 batch.add_rect(0.0, 0.0, 100.0, 50.0, 0.0, 1.0, 0.0, 1.0);
1788
1789 assert_eq!(batch.rects.len(), 1);
1790 assert_eq!(batch.rects[0][2], 100.0);
1791 assert_eq!(batch.rects[0][3], 50.0);
1792 }
1793
1794 #[test]
1795 fn test_batch_add_line() {
1796 let mut batch = DrawBatch::new();
1797 batch.add_line(0.0, 0.0, 100.0, 100.0, 2.0, 0.0, 0.0, 1.0, 1.0);
1798
1799 assert_eq!(batch.lines.len(), 1);
1800 assert_eq!(batch.lines[0][4], 2.0); }
1802
1803 #[test]
1804 fn test_batch_clear() {
1805 let mut batch = DrawBatch::new();
1806 batch.add_circle(0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0);
1807 batch.add_rect(0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0);
1808 batch.add_line(0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0);
1809
1810 batch.clear();
1811
1812 assert!(batch.circles.is_empty());
1813 assert!(batch.rects.is_empty());
1814 assert!(batch.lines.is_empty());
1815 }
1816
1817 #[test]
1818 fn test_batch_draw_call_counts() {
1819 let mut batch = DrawBatch::new();
1820
1821 assert_eq!(batch.unbatched_draw_calls(), 0);
1823 assert_eq!(batch.batched_draw_calls(), 0);
1824
1825 for i in 0..100 {
1827 batch.add_circle(i as f32, 0.0, 5.0, 1.0, 0.0, 0.0, 1.0);
1828 }
1829 for i in 0..50 {
1830 batch.add_rect(i as f32 * 10.0, 0.0, 10.0, 10.0, 0.0, 1.0, 0.0, 1.0);
1831 }
1832
1833 assert_eq!(batch.unbatched_draw_calls(), 150);
1835 assert_eq!(batch.batched_draw_calls(), 2);
1837 }
1838
1839 #[test]
1840 fn test_batch_efficiency() {
1841 let mut batch = DrawBatch::new();
1842
1843 for i in 0..1000 {
1845 batch.add_circle(i as f32, (i as f32).sin() * 100.0, 3.0, 0.2, 0.5, 0.9, 1.0);
1846 }
1847 for i in 0..10 {
1848 batch.add_rect(
1849 i as f32 * 50.0,
1850 0.0,
1851 40.0,
1852 (i as f32 + 1.0) * 20.0,
1853 0.9,
1854 0.3,
1855 0.3,
1856 1.0,
1857 );
1858 }
1859
1860 let reduction = batch.unbatched_draw_calls() as f64 / batch.batched_draw_calls() as f64;
1862 assert!(reduction > 500.0);
1863 }
1864
1865 #[test]
1870 fn test_point2d_default() {
1871 let p: Point2D = Default::default();
1872 assert_eq!(p, Point2D::ORIGIN);
1873 }
1874
1875 #[test]
1876 fn test_point2d_clone() {
1877 let p1 = Point2D::new(3.14, 2.71);
1878 let p2 = p1;
1879 assert_eq!(p1, p2);
1880 }
1881
1882 #[test]
1883 fn test_point2d_debug() {
1884 let p = Point2D::new(1.0, 2.0);
1885 let debug = format!("{p:?}");
1886 assert!(debug.contains("Point2D"));
1887 }
1888
1889 #[test]
1890 fn test_point2d_distance_to_self() {
1891 let p = Point2D::new(5.0, 10.0);
1892 assert_eq!(p.distance(&p), 0.0);
1893 }
1894
1895 #[test]
1896 fn test_point2d_lerp_boundaries() {
1897 let p1 = Point2D::new(0.0, 0.0);
1898 let p2 = Point2D::new(10.0, 10.0);
1899
1900 let at_start = p1.lerp(&p2, 0.0);
1901 assert_eq!(at_start, p1);
1902
1903 let at_end = p1.lerp(&p2, 1.0);
1904 assert_eq!(at_end, p2);
1905 }
1906
1907 #[test]
1908 fn test_point2d_lerp_extrapolate() {
1909 let p1 = Point2D::new(0.0, 0.0);
1910 let p2 = Point2D::new(10.0, 10.0);
1911
1912 let beyond = p1.lerp(&p2, 2.0);
1913 assert!((beyond.x - 20.0).abs() < 1e-10);
1914 assert!((beyond.y - 20.0).abs() < 1e-10);
1915 }
1916
1917 #[test]
1918 fn test_point2d_mul_zero() {
1919 let p = Point2D::new(5.0, 10.0);
1920 let scaled = p * 0.0;
1921 assert_eq!(scaled, Point2D::ORIGIN);
1922 }
1923
1924 #[test]
1925 fn test_point2d_mul_negative() {
1926 let p = Point2D::new(5.0, 10.0);
1927 let scaled = p * -1.0;
1928 assert_eq!(scaled, Point2D::new(-5.0, -10.0));
1929 }
1930
1931 #[test]
1936 fn test_linear_extrapolate_left() {
1937 let interp =
1938 LinearInterpolator::from_points(&[Point2D::new(0.0, 0.0), Point2D::new(10.0, 10.0)]);
1939 let y = interp.interpolate(-5.0);
1941 assert!((y - (-5.0)).abs() < 1e-10);
1942 }
1943
1944 #[test]
1945 fn test_linear_extrapolate_right() {
1946 let interp =
1947 LinearInterpolator::from_points(&[Point2D::new(0.0, 0.0), Point2D::new(10.0, 10.0)]);
1948 let y = interp.interpolate(15.0);
1950 assert!((y - 15.0).abs() < 1e-10);
1951 }
1952
1953 #[test]
1954 fn test_linear_unsorted_input() {
1955 let interp = LinearInterpolator::from_points(&[
1956 Point2D::new(3.0, 30.0),
1957 Point2D::new(1.0, 10.0),
1958 Point2D::new(2.0, 20.0),
1959 ]);
1960 assert!((interp.interpolate(1.5) - 15.0).abs() < 1e-10);
1962 }
1963
1964 #[test]
1965 fn test_linear_points_getter() {
1966 let interp =
1967 LinearInterpolator::from_points(&[Point2D::new(0.0, 0.0), Point2D::new(1.0, 1.0)]);
1968 assert_eq!(interp.points().len(), 2);
1969 }
1970
1971 #[test]
1972 fn test_linear_sample_single_point() {
1973 let interp = LinearInterpolator::from_points(&[Point2D::new(0.0, 5.0)]);
1974 let samples = interp.sample(0.0, 10.0, 5);
1975 for s in &samples {
1977 assert_eq!(s.y, 5.0);
1978 }
1979 }
1980
1981 #[test]
1982 fn test_linear_sample_too_few() {
1983 let interp =
1984 LinearInterpolator::from_points(&[Point2D::new(0.0, 0.0), Point2D::new(10.0, 10.0)]);
1985 let samples = interp.sample(0.0, 10.0, 1);
1986 assert!(samples.is_empty());
1987 }
1988
1989 #[test]
1990 fn test_linear_vertical_segment() {
1991 let interp = LinearInterpolator::from_points(&[
1992 Point2D::new(5.0, 0.0),
1993 Point2D::new(5.0, 10.0), Point2D::new(10.0, 10.0),
1995 ]);
1996 let y = interp.interpolate(5.0);
1998 assert!(y.is_finite());
1999 }
2000
2001 #[test]
2006 fn test_spline_from_xy() {
2007 let xs = [0.0, 1.0, 2.0, 3.0];
2008 let ys = [0.0, 1.0, 0.0, 1.0];
2009 let spline = CubicSpline::from_xy(&xs, &ys);
2010 assert_eq!(spline.points().len(), 4);
2011 }
2012
2013 #[test]
2014 fn test_spline_points_getter() {
2015 let points = vec![
2016 Point2D::new(0.0, 0.0),
2017 Point2D::new(1.0, 1.0),
2018 Point2D::new(2.0, 0.0),
2019 ];
2020 let spline = CubicSpline::from_points(&points);
2021 assert_eq!(spline.points().len(), 3);
2022 }
2023
2024 #[test]
2025 fn test_spline_identical_x() {
2026 let points = vec![
2028 Point2D::new(1.0, 0.0),
2029 Point2D::new(1.0, 10.0),
2030 Point2D::new(2.0, 5.0),
2031 ];
2032 let spline = CubicSpline::from_points(&points);
2033 let y = spline.interpolate(1.0);
2034 assert!(y.is_finite());
2035 }
2036
2037 #[test]
2038 fn test_spline_extrapolate() {
2039 let points = vec![
2040 Point2D::new(0.0, 0.0),
2041 Point2D::new(1.0, 1.0),
2042 Point2D::new(2.0, 0.0),
2043 ];
2044 let spline = CubicSpline::from_points(&points);
2045
2046 let y_before = spline.interpolate(-1.0);
2048 let y_after = spline.interpolate(3.0);
2049 assert!(y_before.is_finite());
2050 assert!(y_after.is_finite());
2051 }
2052
2053 #[test]
2058 fn test_catmull_rom_points_getter() {
2059 let points = vec![Point2D::new(0.0, 0.0), Point2D::new(1.0, 1.0)];
2060 let cr = CatmullRom::from_points(&points);
2061 assert_eq!(cr.points().len(), 2);
2062 }
2063
2064 #[test]
2065 fn test_catmull_rom_to_path_two_points() {
2066 let points = vec![Point2D::new(0.0, 0.0), Point2D::new(1.0, 1.0)];
2067 let cr = CatmullRom::from_points(&points);
2068 let path = cr.to_path(10);
2069 assert_eq!(path.len(), 2); }
2071
2072 #[test]
2073 fn test_catmull_rom_to_path_single() {
2074 let points = vec![Point2D::new(0.0, 0.0)];
2075 let cr = CatmullRom::from_points(&points);
2076 let path = cr.to_path(10);
2077 assert_eq!(path.len(), 1);
2078 }
2079
2080 #[test]
2081 fn test_catmull_rom_tension_clamp() {
2082 let points = vec![
2083 Point2D::new(0.0, 0.0),
2084 Point2D::new(1.0, 1.0),
2085 Point2D::new(2.0, 0.0),
2086 ];
2087
2088 let cr_low = CatmullRom::with_tension(&points, -1.0);
2090 let cr_high = CatmullRom::with_tension(&points, 2.0);
2091
2092 let _ = cr_low.interpolate(0.5);
2094 let _ = cr_high.interpolate(0.5);
2095 }
2096
2097 #[test]
2102 fn test_bezier_clamp_t() {
2103 let bezier = CubicBezier::new(
2104 Point2D::new(0.0, 0.0),
2105 Point2D::new(1.0, 2.0),
2106 Point2D::new(2.0, 2.0),
2107 Point2D::new(3.0, 0.0),
2108 );
2109
2110 let p_neg = bezier.evaluate(-0.5);
2112 let p_over = bezier.evaluate(1.5);
2113
2114 assert_eq!(p_neg, bezier.evaluate(0.0));
2115 assert_eq!(p_over, bezier.evaluate(1.0));
2116 }
2117
2118 #[test]
2119 fn test_bezier_polyline_min_segments() {
2120 let bezier = CubicBezier::new(
2121 Point2D::new(0.0, 0.0),
2122 Point2D::new(1.0, 1.0),
2123 Point2D::new(2.0, 1.0),
2124 Point2D::new(3.0, 0.0),
2125 );
2126
2127 let polyline = bezier.to_polyline(0);
2128 assert!(polyline.len() >= 2);
2129 }
2130
2131 #[test]
2132 fn test_bezier_split_at_zero() {
2133 let bezier = CubicBezier::new(
2134 Point2D::new(0.0, 0.0),
2135 Point2D::new(1.0, 1.0),
2136 Point2D::new(2.0, 1.0),
2137 Point2D::new(3.0, 0.0),
2138 );
2139
2140 let (left, right) = bezier.split(0.0);
2141 assert_eq!(left.p0, bezier.p0);
2142 assert_eq!(right.p3, bezier.p3);
2143 }
2144
2145 #[test]
2146 fn test_bezier_split_at_one() {
2147 let bezier = CubicBezier::new(
2148 Point2D::new(0.0, 0.0),
2149 Point2D::new(1.0, 1.0),
2150 Point2D::new(2.0, 1.0),
2151 Point2D::new(3.0, 0.0),
2152 );
2153
2154 let (left, _right) = bezier.split(1.0);
2155 assert_eq!(left.p3, bezier.p3);
2156 }
2157
2158 #[test]
2159 fn test_bezier_arc_length_zero_segments() {
2160 let bezier = CubicBezier::new(
2161 Point2D::new(0.0, 0.0),
2162 Point2D::new(1.0, 0.0),
2163 Point2D::new(2.0, 0.0),
2164 Point2D::new(3.0, 0.0),
2165 );
2166
2167 let length = bezier.arc_length(0);
2169 assert!(length.is_finite());
2170 }
2171
2172 #[test]
2177 fn test_histogram_zero_bins() {
2178 let hist = HistogramBins::from_data(&[1.0, 2.0, 3.0], 0);
2179 assert_eq!(hist.num_bins(), 0);
2180 }
2181
2182 #[test]
2183 fn test_histogram_all_same_value() {
2184 let data = vec![5.0, 5.0, 5.0, 5.0, 5.0];
2185 let hist = HistogramBins::from_data(&data, 5);
2186 assert_eq!(hist.total_count(), 5);
2187 }
2188
2189 #[test]
2190 fn test_histogram_negative_values() {
2191 let data = vec![-10.0, -5.0, 0.0, 5.0, 10.0];
2192 let hist = HistogramBins::from_data(&data, 4);
2193 assert_eq!(hist.num_bins(), 4);
2194 assert_eq!(hist.total_count(), 5);
2195 }
2196
2197 #[test]
2198 fn test_histogram_bin_range_out_of_bounds() {
2199 let hist = HistogramBins::from_data(&[1.0, 2.0, 3.0], 3);
2200 assert_eq!(hist.bin_range(100), None);
2201 }
2202
2203 #[test]
2204 fn test_histogram_bin_center_out_of_bounds() {
2205 let hist = HistogramBins::from_data(&[1.0, 2.0, 3.0], 3);
2206 assert_eq!(hist.bin_center(100), None);
2207 }
2208
2209 #[test]
2210 fn test_histogram_edge_case_max_value() {
2211 let hist = HistogramBins::from_data_range(&[0.0, 5.0, 10.0], 2, 0.0, 10.0);
2213 assert_eq!(hist.total_count(), 3);
2214 }
2215
2216 #[test]
2221 fn test_arc_negative_angles() {
2222 let arc = ArcGeometry::new(Point2D::ORIGIN, 1.0, -PI / 2.0, PI / 2.0);
2223 assert!((arc.sweep() - PI).abs() < 1e-10);
2224 }
2225
2226 #[test]
2227 fn test_arc_large_angles() {
2228 let arc = ArcGeometry::new(Point2D::ORIGIN, 1.0, 0.0, 4.0 * PI);
2229 let poly = arc.to_polyline(10);
2231 assert_eq!(poly.len(), 11);
2232 }
2233
2234 #[test]
2235 fn test_arc_zero_radius() {
2236 let arc = ArcGeometry::new(Point2D::new(5.0, 5.0), 0.0, 0.0, PI);
2237 let start = arc.start_point();
2238 assert_eq!(start, arc.center);
2239 }
2240
2241 #[test]
2242 fn test_arc_contains_angle_wrap() {
2243 let arc = ArcGeometry::new(Point2D::ORIGIN, 1.0, 3.0 * PI / 2.0, PI / 2.0 + 2.0 * PI);
2245 assert!(arc.contains_angle(0.0));
2246 }
2247
2248 #[test]
2249 fn test_arc_pie_slice_segments() {
2250 let arc = ArcGeometry::new(Point2D::ORIGIN, 1.0, 0.0, PI / 2.0);
2251 let slice = arc.to_pie_slice(8);
2252 assert_eq!(slice.len(), 11);
2254 }
2255
2256 #[test]
2261 fn test_normalizer_zero_range() {
2262 let norm = DataNormalizer::new(5.0, 5.0);
2263 assert_eq!(norm.normalize(5.0), 0.5);
2265 assert_eq!(norm.normalize(10.0), 0.5);
2266 }
2267
2268 #[test]
2269 fn test_normalizer_negative_range() {
2270 let norm = DataNormalizer::new(-100.0, -50.0);
2271 assert!((norm.normalize(-100.0) - 0.0).abs() < 1e-10);
2272 assert!((norm.normalize(-75.0) - 0.5).abs() < 1e-10);
2273 assert!((norm.normalize(-50.0) - 1.0).abs() < 1e-10);
2274 }
2275
2276 #[test]
2277 fn test_normalizer_nice_bounds_small_range() {
2278 let norm = DataNormalizer::new(0.001, 0.002);
2279 let (nice_min, nice_max) = norm.nice_bounds();
2280 assert!(nice_min <= 0.001);
2281 assert!(nice_max >= 0.002);
2282 }
2283
2284 #[test]
2285 fn test_normalizer_nice_bounds_large_range() {
2286 let norm = DataNormalizer::new(0.0, 1_000_000.0);
2287 let (nice_min, nice_max) = norm.nice_bounds();
2288 assert!(nice_min <= 0.0);
2289 assert!(nice_max >= 1_000_000.0);
2290 }
2291
2292 #[test]
2293 fn test_normalizer_from_single_value() {
2294 let norm = DataNormalizer::from_data(&[42.0]);
2295 assert_eq!(norm.min, 42.0);
2297 assert_eq!(norm.max, 42.0);
2298 }
2299
2300 #[test]
2305 fn test_tessellator_tolerance_minimum() {
2306 let tess = PathTessellator::new(0.0001);
2307 assert!(tess.tolerance >= 0.001);
2309 }
2310
2311 #[test]
2312 fn test_tessellator_polygon_too_small() {
2313 let mut tess = PathTessellator::new(0.5);
2314 tess.tessellate_polygon(&[Point2D::new(0.0, 0.0)]);
2315 assert!(tess.vertices.is_empty());
2316
2317 tess.tessellate_polygon(&[Point2D::new(0.0, 0.0), Point2D::new(1.0, 0.0)]);
2318 assert!(tess.vertices.is_empty());
2319 }
2320
2321 #[test]
2322 fn test_tessellator_stroke_too_short() {
2323 let mut tess = PathTessellator::new(0.5);
2324 tess.tessellate_stroke(&[Point2D::new(0.0, 0.0)], 1.0);
2325 assert!(tess.vertices.is_empty());
2326 }
2327
2328 #[test]
2329 fn test_tessellator_stroke_zero_length_segment() {
2330 let mut tess = PathTessellator::new(0.5);
2331 tess.tessellate_stroke(&[Point2D::new(5.0, 5.0), Point2D::new(5.0, 5.0)], 1.0);
2333 assert!(tess.vertices.is_empty());
2335 }
2336
2337 #[test]
2338 fn test_tessellator_circle_min_segments() {
2339 let mut tess = PathTessellator::new(0.5);
2340 tess.tessellate_circle(Point2D::ORIGIN, 1.0, 3);
2341 assert!(tess.vertex_count() >= 9); }
2344
2345 #[test]
2346 fn test_tessellator_default_trait() {
2347 let tess: PathTessellator = Default::default();
2348 assert!(tess.vertices.is_empty());
2349 }
2350
2351 #[test]
2356 fn test_batch_default_trait() {
2357 let batch: DrawBatch = Default::default();
2358 assert!(batch.circles.is_empty());
2359 assert!(batch.rects.is_empty());
2360 assert!(batch.lines.is_empty());
2361 }
2362
2363 #[test]
2364 fn test_batch_only_circles() {
2365 let mut batch = DrawBatch::new();
2366 batch.add_circle(0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0);
2367 assert_eq!(batch.batched_draw_calls(), 1);
2368 }
2369
2370 #[test]
2371 fn test_batch_only_rects() {
2372 let mut batch = DrawBatch::new();
2373 batch.add_rect(0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0);
2374 assert_eq!(batch.batched_draw_calls(), 1);
2375 }
2376
2377 #[test]
2378 fn test_batch_only_lines() {
2379 let mut batch = DrawBatch::new();
2380 batch.add_line(0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0);
2381 assert_eq!(batch.batched_draw_calls(), 1);
2382 }
2383
2384 #[test]
2385 fn test_batch_all_types() {
2386 let mut batch = DrawBatch::new();
2387 batch.add_circle(0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0);
2388 batch.add_rect(0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0);
2389 batch.add_line(0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0);
2390 assert_eq!(batch.batched_draw_calls(), 3);
2391 assert_eq!(batch.unbatched_draw_calls(), 3);
2392 }
2393
2394 #[test]
2395 fn test_batch_debug() {
2396 let batch = DrawBatch::new();
2397 let debug = format!("{batch:?}");
2398 assert!(debug.contains("DrawBatch"));
2399 }
2400
2401 #[test]
2402 fn test_batch_clone() {
2403 let mut batch = DrawBatch::new();
2404 batch.add_circle(1.0, 2.0, 3.0, 1.0, 0.0, 0.0, 1.0);
2405 let cloned = batch.clone();
2406 assert_eq!(cloned.circles.len(), 1);
2407 assert_eq!(cloned.circles[0][0], 1.0);
2408 }
2409}