onedrop_renderer/
warp_mesh.rs1use std::f32::consts::{PI, SQRT_2};
15
16#[derive(Clone, Copy, Debug, PartialEq)]
18pub struct WarpMeshVertex {
19 pub pos_clip: [f32; 2],
21 pub uv_orig: [f32; 2],
23 pub rad: f32,
25 pub ang: f32,
27}
28
29#[derive(Clone, Debug)]
31pub struct WarpMesh {
32 pub cols: u32,
33 pub rows: u32,
34 pub aspect: f32,
37 pub vertices: Vec<WarpMeshVertex>,
39 pub indices: Vec<u32>,
42}
43
44impl WarpMesh {
45 pub fn new(cols: u32, rows: u32, aspect: f32) -> Self {
50 assert!(cols >= 2, "WarpMesh: cols must be >= 2 (got {cols})");
51 assert!(rows >= 2, "WarpMesh: rows must be >= 2 (got {rows})");
52
53 let mut vertices = Vec::with_capacity((cols * rows) as usize);
54 let denom_x = (cols - 1) as f32;
55 let denom_y = (rows - 1) as f32;
56
57 for row in 0..rows {
58 for col in 0..cols {
59 let u = col as f32 / denom_x; let v = row as f32 / denom_y; let dx = u - 0.5;
62 let dy = v - 0.5;
63
64 let rad = (dx * dx + dy * dy).sqrt() * SQRT_2;
68
69 let ang = if dx == 0.0 && dy == 0.0 {
70 0.0
71 } else {
72 let mut a = dy.atan2(dx);
73 if a < 0.0 {
74 a += 2.0 * PI;
75 }
76 a
77 };
78
79 vertices.push(WarpMeshVertex {
80 pos_clip: [u * 2.0 - 1.0, v * 2.0 - 1.0],
81 uv_orig: [u, v],
82 rad,
83 ang,
84 });
85 }
86 }
87
88 let cell_count = (cols - 1) * (rows - 1);
90 let mut indices = Vec::with_capacity((cell_count * 6) as usize);
91 for row in 0..(rows - 1) {
92 for col in 0..(cols - 1) {
93 let i00 = row * cols + col;
94 let i10 = row * cols + (col + 1);
95 let i01 = (row + 1) * cols + col;
96 let i11 = (row + 1) * cols + (col + 1);
97 indices.push(i00);
98 indices.push(i10);
99 indices.push(i01);
100 indices.push(i10);
101 indices.push(i11);
102 indices.push(i01);
103 }
104 }
105
106 Self {
107 cols,
108 rows,
109 aspect,
110 vertices,
111 indices,
112 }
113 }
114
115 pub fn vertex_count(&self) -> usize {
116 self.vertices.len()
117 }
118
119 pub fn index_count(&self) -> usize {
120 self.indices.len()
121 }
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127
128 #[test]
129 fn dimensions_match_grid() {
130 let m = WarpMesh::new(32, 24, 16.0 / 9.0);
131 assert_eq!(m.vertex_count(), 32 * 24);
132 assert_eq!(m.index_count(), (32 - 1) * (24 - 1) * 6);
133 }
134
135 #[test]
136 fn corners_at_clip_extremes() {
137 let m = WarpMesh::new(3, 3, 1.0);
138 assert_eq!(m.vertices[0].pos_clip, [-1.0, -1.0]);
140 assert_eq!(m.vertices[0].uv_orig, [0.0, 0.0]);
141 let last = m.vertices.len() - 1;
143 assert_eq!(m.vertices[last].pos_clip, [1.0, 1.0]);
144 assert_eq!(m.vertices[last].uv_orig, [1.0, 1.0]);
145 }
146
147 #[test]
148 fn corner_rad_is_one() {
149 let m = WarpMesh::new(3, 3, 1.0);
150 let last = m.vertices.len() - 1;
151 assert!((m.vertices[last].rad - 1.0).abs() < 1e-5);
152 }
153
154 #[test]
155 fn center_vertex_has_zero_rad() {
156 let m = WarpMesh::new(3, 3, 1.0);
157 let center = &m.vertices[3 + 1]; assert!(center.rad.abs() < 1e-6);
159 }
160
161 #[test]
162 #[should_panic]
163 fn rejects_degenerate_grid() {
164 let _ = WarpMesh::new(1, 5, 1.0);
165 }
166}