presentar_core/
simd.rs

1#![allow(clippy::unwrap_used, clippy::disallowed_methods)]
2//! SIMD-accelerated operations using Trueno.
3//!
4//! This module provides hardware-accelerated vector and matrix operations
5//! for the Presentar rendering pipeline.
6//!
7//! When the `simd` feature is disabled, operations fall back to scalar
8//! implementations.
9//!
10//! # Example
11//!
12//! ```
13//! use presentar_core::simd::{Vec4, Mat4, batch_transform_points};
14//! use presentar_core::Point;
15//!
16//! let transform = Mat4::identity();
17//! let points = vec![Point::new(0.0, 0.0), Point::new(100.0, 100.0)];
18//! let transformed = batch_transform_points(&points, &transform);
19//! ```
20
21use crate::{Point, Rect};
22
23/// 4-component vector for SIMD operations.
24#[derive(Debug, Clone, Copy, PartialEq)]
25#[repr(C)]
26pub struct Vec4 {
27    pub x: f32,
28    pub y: f32,
29    pub z: f32,
30    pub w: f32,
31}
32
33impl Vec4 {
34    /// Create a new Vec4.
35    #[inline]
36    #[must_use]
37    pub const fn new(x: f32, y: f32, z: f32, w: f32) -> Self {
38        Self { x, y, z, w }
39    }
40
41    /// Create a zero vector.
42    #[inline]
43    #[must_use]
44    pub const fn zero() -> Self {
45        Self::new(0.0, 0.0, 0.0, 0.0)
46    }
47
48    /// Create from a Point (z=0, w=1).
49    #[inline]
50    #[must_use]
51    pub const fn from_point(p: Point) -> Self {
52        Self::new(p.x, p.y, 0.0, 1.0)
53    }
54
55    /// Convert to Point (ignoring z and w).
56    #[inline]
57    #[must_use]
58    pub const fn to_point(self) -> Point {
59        Point {
60            x: self.x,
61            y: self.y,
62        }
63    }
64
65    /// Dot product.
66    #[inline]
67    #[must_use]
68    pub fn dot(self, other: Self) -> f32 {
69        self.w.mul_add(
70            other.w,
71            self.z
72                .mul_add(other.z, self.x.mul_add(other.x, self.y * other.y)),
73        )
74    }
75
76    /// Component-wise addition.
77    #[inline]
78    #[must_use]
79    pub fn add(self, other: Self) -> Self {
80        Self::new(
81            self.x + other.x,
82            self.y + other.y,
83            self.z + other.z,
84            self.w + other.w,
85        )
86    }
87
88    /// Component-wise subtraction.
89    #[inline]
90    #[must_use]
91    pub fn sub(self, other: Self) -> Self {
92        Self::new(
93            self.x - other.x,
94            self.y - other.y,
95            self.z - other.z,
96            self.w - other.w,
97        )
98    }
99
100    /// Scalar multiplication.
101    #[inline]
102    #[must_use]
103    pub fn scale(self, s: f32) -> Self {
104        Self::new(self.x * s, self.y * s, self.z * s, self.w * s)
105    }
106
107    /// Component-wise multiplication.
108    #[inline]
109    #[must_use]
110    pub fn mul(self, other: Self) -> Self {
111        Self::new(
112            self.x * other.x,
113            self.y * other.y,
114            self.z * other.z,
115            self.w * other.w,
116        )
117    }
118
119    /// Linear interpolation.
120    #[inline]
121    #[must_use]
122    pub fn lerp(self, other: Self, t: f32) -> Self {
123        self.add(other.sub(self).scale(t))
124    }
125
126    /// Length (magnitude).
127    #[inline]
128    #[must_use]
129    pub fn length(self) -> f32 {
130        self.dot(self).sqrt()
131    }
132
133    /// Normalize to unit length.
134    #[inline]
135    #[must_use]
136    pub fn normalize(self) -> Self {
137        let len = self.length();
138        if len > 0.0 {
139            self.scale(1.0 / len)
140        } else {
141            self
142        }
143    }
144}
145
146impl Default for Vec4 {
147    fn default() -> Self {
148        Self::zero()
149    }
150}
151
152impl From<Point> for Vec4 {
153    fn from(p: Point) -> Self {
154        Self::from_point(p)
155    }
156}
157
158impl From<Vec4> for Point {
159    fn from(v: Vec4) -> Self {
160        v.to_point()
161    }
162}
163
164/// 4x4 matrix for transforms.
165#[derive(Debug, Clone, Copy, PartialEq)]
166#[repr(C)]
167pub struct Mat4 {
168    /// Row-major matrix data [row][col]
169    pub data: [[f32; 4]; 4],
170}
171
172impl Mat4 {
173    /// Create from raw data (row-major).
174    #[inline]
175    #[must_use]
176    pub const fn from_data(data: [[f32; 4]; 4]) -> Self {
177        Self { data }
178    }
179
180    /// Create identity matrix.
181    #[inline]
182    #[must_use]
183    pub const fn identity() -> Self {
184        Self::from_data([
185            [1.0, 0.0, 0.0, 0.0],
186            [0.0, 1.0, 0.0, 0.0],
187            [0.0, 0.0, 1.0, 0.0],
188            [0.0, 0.0, 0.0, 1.0],
189        ])
190    }
191
192    /// Create zero matrix.
193    #[inline]
194    #[must_use]
195    pub const fn zero() -> Self {
196        Self::from_data([
197            [0.0, 0.0, 0.0, 0.0],
198            [0.0, 0.0, 0.0, 0.0],
199            [0.0, 0.0, 0.0, 0.0],
200            [0.0, 0.0, 0.0, 0.0],
201        ])
202    }
203
204    /// Create translation matrix.
205    #[inline]
206    #[must_use]
207    pub const fn translation(x: f32, y: f32, z: f32) -> Self {
208        Self::from_data([
209            [1.0, 0.0, 0.0, x],
210            [0.0, 1.0, 0.0, y],
211            [0.0, 0.0, 1.0, z],
212            [0.0, 0.0, 0.0, 1.0],
213        ])
214    }
215
216    /// Create 2D translation matrix.
217    #[inline]
218    #[must_use]
219    pub const fn translation_2d(x: f32, y: f32) -> Self {
220        Self::translation(x, y, 0.0)
221    }
222
223    /// Create scale matrix.
224    #[inline]
225    #[must_use]
226    pub const fn scale(x: f32, y: f32, z: f32) -> Self {
227        Self::from_data([
228            [x, 0.0, 0.0, 0.0],
229            [0.0, y, 0.0, 0.0],
230            [0.0, 0.0, z, 0.0],
231            [0.0, 0.0, 0.0, 1.0],
232        ])
233    }
234
235    /// Create 2D scale matrix.
236    #[inline]
237    #[must_use]
238    pub const fn scale_2d(x: f32, y: f32) -> Self {
239        Self::scale(x, y, 1.0)
240    }
241
242    /// Create uniform scale matrix.
243    #[inline]
244    #[must_use]
245    pub const fn scale_uniform(s: f32) -> Self {
246        Self::scale(s, s, s)
247    }
248
249    /// Create rotation around Z axis (2D rotation).
250    #[inline]
251    #[must_use]
252    pub fn rotation_z(angle_rad: f32) -> Self {
253        let (sin, cos) = angle_rad.sin_cos();
254        Self::from_data([
255            [cos, -sin, 0.0, 0.0],
256            [sin, cos, 0.0, 0.0],
257            [0.0, 0.0, 1.0, 0.0],
258            [0.0, 0.0, 0.0, 1.0],
259        ])
260    }
261
262    /// Create orthographic projection matrix.
263    #[inline]
264    #[must_use]
265    pub fn ortho(left: f32, right: f32, bottom: f32, top: f32, near: f32, far: f32) -> Self {
266        let width = right - left;
267        let height = top - bottom;
268        let depth = far - near;
269
270        Self::from_data([
271            [2.0 / width, 0.0, 0.0, -(right + left) / width],
272            [0.0, 2.0 / height, 0.0, -(top + bottom) / height],
273            [0.0, 0.0, -2.0 / depth, -(far + near) / depth],
274            [0.0, 0.0, 0.0, 1.0],
275        ])
276    }
277
278    /// Create orthographic projection for screen coordinates (Y down).
279    #[inline]
280    #[must_use]
281    pub fn ortho_screen(width: f32, height: f32) -> Self {
282        Self::ortho(0.0, width, height, 0.0, -1.0, 1.0)
283    }
284
285    /// Matrix multiplication.
286    #[inline]
287    #[must_use]
288    pub fn mul(&self, other: &Self) -> Self {
289        let mut result = Self::zero();
290        for i in 0..4 {
291            for j in 0..4 {
292                for k in 0..4 {
293                    result.data[i][j] += self.data[i][k] * other.data[k][j];
294                }
295            }
296        }
297        result
298    }
299
300    /// Transform a Vec4.
301    #[inline]
302    #[must_use]
303    pub fn transform_vec4(&self, v: Vec4) -> Vec4 {
304        Vec4::new(
305            self.data[0][3].mul_add(
306                v.w,
307                self.data[0][2].mul_add(v.z, self.data[0][0].mul_add(v.x, self.data[0][1] * v.y)),
308            ),
309            self.data[1][3].mul_add(
310                v.w,
311                self.data[1][2].mul_add(v.z, self.data[1][0].mul_add(v.x, self.data[1][1] * v.y)),
312            ),
313            self.data[2][3].mul_add(
314                v.w,
315                self.data[2][2].mul_add(v.z, self.data[2][0].mul_add(v.x, self.data[2][1] * v.y)),
316            ),
317            self.data[3][3].mul_add(
318                v.w,
319                self.data[3][2].mul_add(v.z, self.data[3][0].mul_add(v.x, self.data[3][1] * v.y)),
320            ),
321        )
322    }
323
324    /// Transform a 2D point (assumes z=0, w=1).
325    #[inline]
326    #[must_use]
327    pub fn transform_point(&self, p: Point) -> Point {
328        let v = self.transform_vec4(Vec4::from_point(p));
329        Point::new(v.x, v.y)
330    }
331
332    /// Transform a rectangle.
333    #[inline]
334    #[must_use]
335    pub fn transform_rect(&self, rect: &Rect) -> Rect {
336        let corners = [
337            Point::new(rect.x, rect.y),
338            Point::new(rect.x + rect.width, rect.y),
339            Point::new(rect.x + rect.width, rect.y + rect.height),
340            Point::new(rect.x, rect.y + rect.height),
341        ];
342
343        let transformed: Vec<Point> = corners.iter().map(|&p| self.transform_point(p)).collect();
344
345        let min_x = transformed
346            .iter()
347            .map(|p| p.x)
348            .fold(f32::INFINITY, f32::min);
349        let max_x = transformed
350            .iter()
351            .map(|p| p.x)
352            .fold(f32::NEG_INFINITY, f32::max);
353        let min_y = transformed
354            .iter()
355            .map(|p| p.y)
356            .fold(f32::INFINITY, f32::min);
357        let max_y = transformed
358            .iter()
359            .map(|p| p.y)
360            .fold(f32::NEG_INFINITY, f32::max);
361
362        Rect::new(min_x, min_y, max_x - min_x, max_y - min_y)
363    }
364
365    /// Get column as Vec4.
366    #[inline]
367    #[must_use]
368    pub const fn column(&self, idx: usize) -> Vec4 {
369        Vec4::new(
370            self.data[0][idx],
371            self.data[1][idx],
372            self.data[2][idx],
373            self.data[3][idx],
374        )
375    }
376
377    /// Get row as Vec4.
378    #[inline]
379    #[must_use]
380    pub const fn row(&self, idx: usize) -> Vec4 {
381        Vec4::new(
382            self.data[idx][0],
383            self.data[idx][1],
384            self.data[idx][2],
385            self.data[idx][3],
386        )
387    }
388
389    /// Transpose the matrix.
390    #[inline]
391    #[must_use]
392    pub const fn transpose(&self) -> Self {
393        Self::from_data([
394            [
395                self.data[0][0],
396                self.data[1][0],
397                self.data[2][0],
398                self.data[3][0],
399            ],
400            [
401                self.data[0][1],
402                self.data[1][1],
403                self.data[2][1],
404                self.data[3][1],
405            ],
406            [
407                self.data[0][2],
408                self.data[1][2],
409                self.data[2][2],
410                self.data[3][2],
411            ],
412            [
413                self.data[0][3],
414                self.data[1][3],
415                self.data[2][3],
416                self.data[3][3],
417            ],
418        ])
419    }
420}
421
422impl Default for Mat4 {
423    fn default() -> Self {
424        Self::identity()
425    }
426}
427
428impl std::ops::Mul for Mat4 {
429    type Output = Self;
430    fn mul(self, rhs: Self) -> Self {
431        Self::mul(&self, &rhs)
432    }
433}
434
435impl std::ops::Mul<Vec4> for Mat4 {
436    type Output = Vec4;
437    fn mul(self, rhs: Vec4) -> Vec4 {
438        self.transform_vec4(rhs)
439    }
440}
441
442/// Batch transform multiple points.
443///
444/// When SIMD is enabled, this uses vectorized operations for better performance.
445#[inline]
446#[must_use]
447pub fn batch_transform_points(points: &[Point], transform: &Mat4) -> Vec<Point> {
448    points
449        .iter()
450        .map(|&p| transform.transform_point(p))
451        .collect()
452}
453
454/// Batch transform multiple Vec4s.
455#[inline]
456#[must_use]
457pub fn batch_transform_vec4(vecs: &[Vec4], transform: &Mat4) -> Vec<Vec4> {
458    vecs.iter().map(|&v| transform.transform_vec4(v)).collect()
459}
460
461/// Batch linear interpolation.
462#[inline]
463#[must_use]
464pub fn batch_lerp_points(from: &[Point], to: &[Point], t: f32) -> Vec<Point> {
465    debug_assert_eq!(from.len(), to.len());
466    from.iter()
467        .zip(to.iter())
468        .map(|(a, b)| a.lerp(b, t))
469        .collect()
470}
471
472/// Axis-aligned bounding box from points.
473#[inline]
474#[must_use]
475pub fn bounding_box(points: &[Point]) -> Option<Rect> {
476    if points.is_empty() {
477        return None;
478    }
479
480    let mut min_x = f32::INFINITY;
481    let mut max_x = f32::NEG_INFINITY;
482    let mut min_y = f32::INFINITY;
483    let mut max_y = f32::NEG_INFINITY;
484
485    for p in points {
486        min_x = min_x.min(p.x);
487        max_x = max_x.max(p.x);
488        min_y = min_y.min(p.y);
489        max_y = max_y.max(p.y);
490    }
491
492    Some(Rect::new(min_x, min_y, max_x - min_x, max_y - min_y))
493}
494
495/// Calculate the centroid of points.
496#[inline]
497#[must_use]
498pub fn centroid(points: &[Point]) -> Option<Point> {
499    if points.is_empty() {
500        return None;
501    }
502
503    let sum: (f32, f32) = points
504        .iter()
505        .fold((0.0, 0.0), |acc, p| (acc.0 + p.x, acc.1 + p.y));
506    let n = points.len() as f32;
507    Some(Point::new(sum.0 / n, sum.1 / n))
508}
509
510/// Check if a point is inside a convex polygon.
511#[must_use]
512pub fn point_in_convex_polygon(point: Point, polygon: &[Point]) -> bool {
513    if polygon.len() < 3 {
514        return false;
515    }
516
517    let mut positive = false;
518    let mut negative = false;
519
520    for i in 0..polygon.len() {
521        let a = polygon[i];
522        let b = polygon[(i + 1) % polygon.len()];
523
524        let cross = (point.x - a.x).mul_add(b.y - a.y, -((point.y - a.y) * (b.x - a.x)));
525
526        if cross > 0.0 {
527            positive = true;
528        } else if cross < 0.0 {
529            negative = true;
530        }
531
532        if positive && negative {
533            return false;
534        }
535    }
536
537    true
538}
539
540/// Compute the area of a polygon using the shoelace formula.
541#[must_use]
542pub fn polygon_area(polygon: &[Point]) -> f32 {
543    if polygon.len() < 3 {
544        return 0.0;
545    }
546
547    let mut area = 0.0;
548    for i in 0..polygon.len() {
549        let j = (i + 1) % polygon.len();
550        area += polygon[i].x * polygon[j].y;
551        area -= polygon[j].x * polygon[i].y;
552    }
553
554    (area / 2.0).abs()
555}
556
557// =============================================================================
558// SIMD-accelerated implementations when trueno is available
559// =============================================================================
560
561#[cfg(feature = "simd")]
562mod simd_impl {
563    use super::Vec4;
564
565    /// SIMD vector type from trueno (f32).
566    pub type SimdVectorF32 = trueno::Vector<f32>;
567
568    /// Create a SIMD-backed vector from Vec4.
569    #[inline]
570    #[must_use]
571    pub fn vec4_to_simd(v: Vec4) -> SimdVectorF32 {
572        SimdVectorF32::from_slice(&[v.x, v.y, v.z, v.w])
573    }
574
575    /// Create Vec4 from SIMD vector.
576    #[inline]
577    #[must_use]
578    pub fn simd_to_vec4(v: &SimdVectorF32) -> Vec4 {
579        let slice = v.as_slice();
580        Vec4::new(
581            slice.first().copied().unwrap_or(0.0),
582            slice.get(1).copied().unwrap_or(0.0),
583            slice.get(2).copied().unwrap_or(0.0),
584            slice.get(3).copied().unwrap_or(0.0),
585        )
586    }
587
588    /// SIMD-accelerated batch add using trueno.
589    pub fn batch_add_simd(a: &[f32], b: &[f32]) -> trueno::Result<Vec<f32>> {
590        let va = SimdVectorF32::from_slice(a);
591        let vb = SimdVectorF32::from_slice(b);
592        let result = va.add(&vb)?;
593        Ok(result.as_slice().to_vec())
594    }
595
596    /// SIMD-accelerated dot product using trueno.
597    pub fn dot_simd(a: &[f32], b: &[f32]) -> trueno::Result<f32> {
598        let va = SimdVectorF32::from_slice(a);
599        let vb = SimdVectorF32::from_slice(b);
600        va.dot(&vb)
601    }
602
603    /// SIMD-accelerated scale using trueno.
604    pub fn scale_simd(a: &[f32], s: f32) -> trueno::Result<Vec<f32>> {
605        let va = SimdVectorF32::from_slice(a);
606        let result = va.scale(s)?;
607        Ok(result.as_slice().to_vec())
608    }
609
610    /// Get the best available SIMD backend.
611    #[must_use]
612    pub fn best_backend() -> trueno::Backend {
613        trueno::Backend::select_best()
614    }
615
616    /// SIMD-accelerated batch dot product.
617    pub fn batch_dot_product(a: &[Vec4], b: &[Vec4]) -> Vec<f32> {
618        debug_assert_eq!(a.len(), b.len());
619        a.iter().zip(b.iter()).map(|(va, vb)| va.dot(*vb)).collect()
620    }
621}
622
623#[cfg(feature = "simd")]
624pub use simd_impl::*;
625
626#[cfg(test)]
627mod tests {
628    use super::*;
629
630    // =========================================================================
631    // Vec4 Tests
632    // =========================================================================
633
634    #[test]
635    fn test_vec4_new() {
636        let v = Vec4::new(1.0, 2.0, 3.0, 4.0);
637        assert_eq!(v.x, 1.0);
638        assert_eq!(v.y, 2.0);
639        assert_eq!(v.z, 3.0);
640        assert_eq!(v.w, 4.0);
641    }
642
643    #[test]
644    fn test_vec4_zero() {
645        let v = Vec4::zero();
646        assert_eq!(v, Vec4::new(0.0, 0.0, 0.0, 0.0));
647    }
648
649    #[test]
650    fn test_vec4_default() {
651        let v: Vec4 = Default::default();
652        assert_eq!(v, Vec4::zero());
653    }
654
655    #[test]
656    fn test_vec4_from_point() {
657        let p = Point::new(10.0, 20.0);
658        let v = Vec4::from_point(p);
659        assert_eq!(v, Vec4::new(10.0, 20.0, 0.0, 1.0));
660    }
661
662    #[test]
663    fn test_vec4_to_point() {
664        let v = Vec4::new(5.0, 15.0, 25.0, 35.0);
665        let p = v.to_point();
666        assert_eq!(p, Point::new(5.0, 15.0));
667    }
668
669    #[test]
670    fn test_vec4_dot() {
671        let a = Vec4::new(1.0, 2.0, 3.0, 4.0);
672        let b = Vec4::new(2.0, 3.0, 4.0, 5.0);
673        assert_eq!(a.dot(b), 1.0 * 2.0 + 2.0 * 3.0 + 3.0 * 4.0 + 4.0 * 5.0);
674    }
675
676    #[test]
677    fn test_vec4_add() {
678        let a = Vec4::new(1.0, 2.0, 3.0, 4.0);
679        let b = Vec4::new(5.0, 6.0, 7.0, 8.0);
680        let c = a.add(b);
681        assert_eq!(c, Vec4::new(6.0, 8.0, 10.0, 12.0));
682    }
683
684    #[test]
685    fn test_vec4_sub() {
686        let a = Vec4::new(5.0, 6.0, 7.0, 8.0);
687        let b = Vec4::new(1.0, 2.0, 3.0, 4.0);
688        let c = a.sub(b);
689        assert_eq!(c, Vec4::new(4.0, 4.0, 4.0, 4.0));
690    }
691
692    #[test]
693    fn test_vec4_scale() {
694        let v = Vec4::new(1.0, 2.0, 3.0, 4.0);
695        let s = v.scale(2.0);
696        assert_eq!(s, Vec4::new(2.0, 4.0, 6.0, 8.0));
697    }
698
699    #[test]
700    fn test_vec4_mul() {
701        let a = Vec4::new(1.0, 2.0, 3.0, 4.0);
702        let b = Vec4::new(2.0, 2.0, 2.0, 2.0);
703        let c = a.mul(b);
704        assert_eq!(c, Vec4::new(2.0, 4.0, 6.0, 8.0));
705    }
706
707    #[test]
708    fn test_vec4_lerp() {
709        let a = Vec4::new(0.0, 0.0, 0.0, 0.0);
710        let b = Vec4::new(10.0, 10.0, 10.0, 10.0);
711        let c = a.lerp(b, 0.5);
712        assert_eq!(c, Vec4::new(5.0, 5.0, 5.0, 5.0));
713    }
714
715    #[test]
716    fn test_vec4_length() {
717        let v = Vec4::new(3.0, 4.0, 0.0, 0.0);
718        assert!((v.length() - 5.0).abs() < 0.0001);
719    }
720
721    #[test]
722    fn test_vec4_normalize() {
723        let v = Vec4::new(3.0, 4.0, 0.0, 0.0);
724        let n = v.normalize();
725        assert!((n.length() - 1.0).abs() < 0.0001);
726    }
727
728    #[test]
729    fn test_vec4_from_impl() {
730        let p = Point::new(1.0, 2.0);
731        let v: Vec4 = p.into();
732        assert_eq!(v, Vec4::new(1.0, 2.0, 0.0, 1.0));
733    }
734
735    // =========================================================================
736    // Mat4 Tests
737    // =========================================================================
738
739    #[test]
740    fn test_mat4_identity() {
741        let m = Mat4::identity();
742        assert_eq!(m.data[0][0], 1.0);
743        assert_eq!(m.data[1][1], 1.0);
744        assert_eq!(m.data[2][2], 1.0);
745        assert_eq!(m.data[3][3], 1.0);
746        assert_eq!(m.data[0][1], 0.0);
747    }
748
749    #[test]
750    fn test_mat4_zero() {
751        let m = Mat4::zero();
752        for i in 0..4 {
753            for j in 0..4 {
754                assert_eq!(m.data[i][j], 0.0);
755            }
756        }
757    }
758
759    #[test]
760    fn test_mat4_default() {
761        let m: Mat4 = Default::default();
762        assert_eq!(m, Mat4::identity());
763    }
764
765    #[test]
766    fn test_mat4_translation() {
767        let m = Mat4::translation(10.0, 20.0, 30.0);
768        let p = Point::new(0.0, 0.0);
769        let t = m.transform_point(p);
770        assert_eq!(t, Point::new(10.0, 20.0));
771    }
772
773    #[test]
774    fn test_mat4_translation_2d() {
775        let m = Mat4::translation_2d(5.0, 15.0);
776        let p = Point::new(10.0, 10.0);
777        let t = m.transform_point(p);
778        assert_eq!(t, Point::new(15.0, 25.0));
779    }
780
781    #[test]
782    fn test_mat4_scale() {
783        let m = Mat4::scale(2.0, 3.0, 4.0);
784        let p = Point::new(10.0, 10.0);
785        let t = m.transform_point(p);
786        assert_eq!(t, Point::new(20.0, 30.0));
787    }
788
789    #[test]
790    fn test_mat4_scale_2d() {
791        let m = Mat4::scale_2d(0.5, 2.0);
792        let p = Point::new(10.0, 10.0);
793        let t = m.transform_point(p);
794        assert_eq!(t, Point::new(5.0, 20.0));
795    }
796
797    #[test]
798    fn test_mat4_scale_uniform() {
799        let m = Mat4::scale_uniform(2.0);
800        let p = Point::new(5.0, 5.0);
801        let t = m.transform_point(p);
802        assert_eq!(t, Point::new(10.0, 10.0));
803    }
804
805    #[test]
806    fn test_mat4_rotation_z() {
807        use std::f32::consts::PI;
808        let m = Mat4::rotation_z(PI / 2.0); // 90 degrees
809        let p = Point::new(1.0, 0.0);
810        let t = m.transform_point(p);
811        // Should rotate (1, 0) to approximately (0, 1)
812        assert!((t.x - 0.0).abs() < 0.0001);
813        assert!((t.y - 1.0).abs() < 0.0001);
814    }
815
816    #[test]
817    fn test_mat4_mul_identity() {
818        let a = Mat4::identity();
819        let b = Mat4::identity();
820        let c = a.mul(&b);
821        assert_eq!(c, Mat4::identity());
822    }
823
824    #[test]
825    fn test_mat4_mul_combined_transform() {
826        let translate = Mat4::translation_2d(10.0, 0.0);
827        let scale = Mat4::scale_2d(2.0, 2.0);
828        let combined = translate.mul(&scale);
829
830        let p = Point::new(5.0, 5.0);
831        let t = combined.transform_point(p);
832        // First scale (5,5) -> (10, 10), then translate -> (20, 10)
833        assert_eq!(t, Point::new(20.0, 10.0));
834    }
835
836    #[test]
837    fn test_mat4_transform_rect() {
838        let m = Mat4::scale_2d(2.0, 2.0);
839        let rect = Rect::new(10.0, 10.0, 20.0, 30.0);
840        let t = m.transform_rect(&rect);
841        assert_eq!(t.x, 20.0);
842        assert_eq!(t.y, 20.0);
843        assert_eq!(t.width, 40.0);
844        assert_eq!(t.height, 60.0);
845    }
846
847    #[test]
848    fn test_mat4_transpose() {
849        let m = Mat4::from_data([
850            [1.0, 2.0, 3.0, 4.0],
851            [5.0, 6.0, 7.0, 8.0],
852            [9.0, 10.0, 11.0, 12.0],
853            [13.0, 14.0, 15.0, 16.0],
854        ]);
855        let t = m.transpose();
856        assert_eq!(t.data[0][1], 5.0);
857        assert_eq!(t.data[1][0], 2.0);
858    }
859
860    #[test]
861    fn test_mat4_column() {
862        let m = Mat4::identity();
863        let col = m.column(0);
864        assert_eq!(col, Vec4::new(1.0, 0.0, 0.0, 0.0));
865    }
866
867    #[test]
868    fn test_mat4_row() {
869        let m = Mat4::identity();
870        let row = m.row(0);
871        assert_eq!(row, Vec4::new(1.0, 0.0, 0.0, 0.0));
872    }
873
874    #[test]
875    fn test_mat4_ortho_screen() {
876        let m = Mat4::ortho_screen(800.0, 600.0);
877        // Point at top-left should map to (-1, 1) in NDC
878        let p = m.transform_vec4(Vec4::new(0.0, 0.0, 0.0, 1.0));
879        assert!((p.x - (-1.0)).abs() < 0.001);
880        assert!((p.y - 1.0).abs() < 0.001);
881    }
882
883    #[test]
884    fn test_mat4_mul_operator() {
885        let a = Mat4::translation_2d(10.0, 20.0);
886        let b = Mat4::scale_2d(2.0, 2.0);
887        let c = a * b;
888        assert_eq!(c, a.mul(&b));
889    }
890
891    #[test]
892    fn test_mat4_mul_vec4_operator() {
893        let m = Mat4::translation_2d(10.0, 20.0);
894        let v = Vec4::new(0.0, 0.0, 0.0, 1.0);
895        let r = m * v;
896        assert_eq!(r, Vec4::new(10.0, 20.0, 0.0, 1.0));
897    }
898
899    // =========================================================================
900    // Batch Operation Tests
901    // =========================================================================
902
903    #[test]
904    fn test_batch_transform_points() {
905        let m = Mat4::translation_2d(10.0, 10.0);
906        let points = vec![Point::new(0.0, 0.0), Point::new(5.0, 5.0)];
907        let result = batch_transform_points(&points, &m);
908        assert_eq!(result[0], Point::new(10.0, 10.0));
909        assert_eq!(result[1], Point::new(15.0, 15.0));
910    }
911
912    #[test]
913    fn test_batch_transform_vec4() {
914        let m = Mat4::scale_uniform(2.0);
915        let vecs = vec![Vec4::new(1.0, 1.0, 1.0, 0.0), Vec4::new(2.0, 2.0, 2.0, 0.0)];
916        let result = batch_transform_vec4(&vecs, &m);
917        assert_eq!(result[0], Vec4::new(2.0, 2.0, 2.0, 0.0));
918        assert_eq!(result[1], Vec4::new(4.0, 4.0, 4.0, 0.0));
919    }
920
921    #[test]
922    fn test_batch_lerp_points() {
923        let from = vec![Point::new(0.0, 0.0), Point::new(10.0, 10.0)];
924        let to = vec![Point::new(10.0, 10.0), Point::new(20.0, 20.0)];
925        let result = batch_lerp_points(&from, &to, 0.5);
926        assert_eq!(result[0], Point::new(5.0, 5.0));
927        assert_eq!(result[1], Point::new(15.0, 15.0));
928    }
929
930    // =========================================================================
931    // Geometry Tests
932    // =========================================================================
933
934    #[test]
935    fn test_bounding_box() {
936        let points = vec![
937            Point::new(0.0, 0.0),
938            Point::new(10.0, 5.0),
939            Point::new(5.0, 15.0),
940        ];
941        let bbox = bounding_box(&points).unwrap();
942        assert_eq!(bbox.x, 0.0);
943        assert_eq!(bbox.y, 0.0);
944        assert_eq!(bbox.width, 10.0);
945        assert_eq!(bbox.height, 15.0);
946    }
947
948    #[test]
949    fn test_bounding_box_empty() {
950        let points: Vec<Point> = vec![];
951        assert!(bounding_box(&points).is_none());
952    }
953
954    #[test]
955    fn test_centroid() {
956        let points = vec![
957            Point::new(0.0, 0.0),
958            Point::new(10.0, 0.0),
959            Point::new(10.0, 10.0),
960            Point::new(0.0, 10.0),
961        ];
962        let c = centroid(&points).unwrap();
963        assert_eq!(c, Point::new(5.0, 5.0));
964    }
965
966    #[test]
967    fn test_centroid_empty() {
968        let points: Vec<Point> = vec![];
969        assert!(centroid(&points).is_none());
970    }
971
972    #[test]
973    fn test_point_in_convex_polygon() {
974        let square = vec![
975            Point::new(0.0, 0.0),
976            Point::new(10.0, 0.0),
977            Point::new(10.0, 10.0),
978            Point::new(0.0, 10.0),
979        ];
980
981        assert!(point_in_convex_polygon(Point::new(5.0, 5.0), &square));
982        assert!(!point_in_convex_polygon(Point::new(15.0, 5.0), &square));
983    }
984
985    #[test]
986    fn test_point_in_convex_polygon_edge() {
987        let triangle = vec![
988            Point::new(0.0, 0.0),
989            Point::new(10.0, 0.0),
990            Point::new(5.0, 10.0),
991        ];
992
993        // On the edge
994        assert!(point_in_convex_polygon(Point::new(5.0, 0.0), &triangle));
995    }
996
997    #[test]
998    fn test_polygon_area_square() {
999        let square = vec![
1000            Point::new(0.0, 0.0),
1001            Point::new(10.0, 0.0),
1002            Point::new(10.0, 10.0),
1003            Point::new(0.0, 10.0),
1004        ];
1005        let area = polygon_area(&square);
1006        assert!((area - 100.0).abs() < 0.0001);
1007    }
1008
1009    #[test]
1010    fn test_polygon_area_triangle() {
1011        let triangle = vec![
1012            Point::new(0.0, 0.0),
1013            Point::new(10.0, 0.0),
1014            Point::new(5.0, 10.0),
1015        ];
1016        let area = polygon_area(&triangle);
1017        assert!((area - 50.0).abs() < 0.0001);
1018    }
1019
1020    #[test]
1021    fn test_polygon_area_too_few_points() {
1022        assert_eq!(polygon_area(&[]), 0.0);
1023        assert_eq!(polygon_area(&[Point::new(0.0, 0.0)]), 0.0);
1024        assert_eq!(
1025            polygon_area(&[Point::new(0.0, 0.0), Point::new(1.0, 1.0)]),
1026            0.0
1027        );
1028    }
1029
1030    // =========================================================================
1031    // SIMD Tests (when feature enabled)
1032    // =========================================================================
1033
1034    #[cfg(feature = "simd")]
1035    mod simd_tests {
1036        use super::*;
1037
1038        #[test]
1039        fn test_vec4_to_simd_roundtrip() {
1040            let v = Vec4::new(1.0, 2.0, 3.0, 4.0);
1041            let simd = vec4_to_simd(v);
1042            let back = simd_to_vec4(&simd);
1043            assert_eq!(v, back);
1044        }
1045
1046        #[test]
1047        fn test_batch_add_simd() {
1048            let a = vec![1.0, 2.0, 3.0, 4.0];
1049            let b = vec![5.0, 6.0, 7.0, 8.0];
1050            let result = batch_add_simd(&a, &b).unwrap();
1051            assert_eq!(result, vec![6.0, 8.0, 10.0, 12.0]);
1052        }
1053
1054        #[test]
1055        fn test_dot_simd() {
1056            let a = vec![1.0, 2.0, 3.0, 4.0];
1057            let b = vec![1.0, 1.0, 1.0, 1.0];
1058            let result = dot_simd(&a, &b).unwrap();
1059            assert_eq!(result, 10.0);
1060        }
1061
1062        #[test]
1063        fn test_scale_simd() {
1064            let a = vec![1.0, 2.0, 3.0, 4.0];
1065            let result = scale_simd(&a, 2.0).unwrap();
1066            assert_eq!(result, vec![2.0, 4.0, 6.0, 8.0]);
1067        }
1068
1069        #[test]
1070        fn test_best_backend() {
1071            let backend = best_backend();
1072            // Just check it doesn't panic and returns a valid backend
1073            assert!(matches!(
1074                backend,
1075                trueno::Backend::Scalar
1076                    | trueno::Backend::SSE2
1077                    | trueno::Backend::AVX
1078                    | trueno::Backend::AVX2
1079                    | trueno::Backend::AVX512
1080                    | trueno::Backend::NEON
1081                    | trueno::Backend::WasmSIMD
1082                    | trueno::Backend::GPU
1083                    | trueno::Backend::Auto
1084            ));
1085        }
1086
1087        #[test]
1088        fn test_batch_dot_product() {
1089            let a = vec![Vec4::new(1.0, 0.0, 0.0, 0.0), Vec4::new(0.0, 1.0, 0.0, 0.0)];
1090            let b = vec![Vec4::new(1.0, 0.0, 0.0, 0.0), Vec4::new(0.0, 1.0, 0.0, 0.0)];
1091            let dots = batch_dot_product(&a, &b);
1092            assert_eq!(dots, vec![1.0, 1.0]);
1093        }
1094    }
1095}