1use super::hash::hash_unit;
13
14#[derive(Clone, Copy)]
20pub struct PatternLayout<'a> {
21 pub kind: &'a str,
24 pub bounds_w: f64,
26 pub bounds_h: f64,
28 pub spacing: Option<f64>,
31 pub count: Option<i64>,
34 pub seed: i64,
36 pub jitter: f64,
39}
40
41const JITTER_Y_SEED_MIX: i64 = 0x5555;
44
45pub fn pattern_positions(p: PatternLayout<'_>) -> Vec<(f64, f64)> {
60 match p.kind {
61 "grid" => grid_positions(p),
62 "scatter" => scatter_positions(p),
63 _ => Vec::new(),
64 }
65}
66
67fn grid_positions(p: PatternLayout<'_>) -> Vec<(f64, f64)> {
68 let s = match p.spacing.filter(|&v| v > 0.0) {
69 Some(v) => v,
70 None => return Vec::new(),
71 };
72
73 let mut positions = Vec::new();
74 let mut row: i64 = 0;
75 while (row as f64) * s < p.bounds_h {
76 let mut col: i64 = 0;
77 while (col as f64) * s < p.bounds_w {
78 let base_x = (col as f64) * s;
79 let base_y = (row as f64) * s;
80 let (jx, jy) = if p.jitter > 0.0 {
81 let jx = (hash_unit(col, row, p.seed) * 2.0 - 1.0) * p.jitter * s;
82 let jy =
83 (hash_unit(col, row, p.seed ^ JITTER_Y_SEED_MIX) * 2.0 - 1.0) * p.jitter * s;
84 (jx, jy)
85 } else {
86 (0.0, 0.0)
87 };
88 positions.push((base_x + jx, base_y + jy));
89 col += 1;
90 }
91 row += 1;
92 }
93 positions
94}
95
96fn scatter_positions(p: PatternLayout<'_>) -> Vec<(f64, f64)> {
97 let count = match p.count.filter(|&v| v > 0) {
98 Some(v) => v,
99 None => return Vec::new(),
100 };
101
102 let mut positions = Vec::with_capacity(count as usize);
103 for i in 0..count {
104 let ox = hash_unit(i, 0, p.seed) * p.bounds_w;
105 let oy = hash_unit(i, 1, p.seed) * p.bounds_h;
106 positions.push((ox, oy));
107 }
108 positions
109}
110
111#[cfg(test)]
112mod tests {
113 use super::*;
114
115 fn layout_grid(
116 bw: f64,
117 bh: f64,
118 spacing: f64,
119 jitter: f64,
120 seed: i64,
121 ) -> PatternLayout<'static> {
122 PatternLayout {
123 kind: "grid",
124 bounds_w: bw,
125 bounds_h: bh,
126 spacing: Some(spacing),
127 count: None,
128 seed,
129 jitter,
130 }
131 }
132
133 fn layout_scatter(bw: f64, bh: f64, count: i64, seed: i64) -> PatternLayout<'static> {
134 PatternLayout {
135 kind: "scatter",
136 bounds_w: bw,
137 bounds_h: bh,
138 spacing: None,
139 count: Some(count),
140 seed,
141 jitter: 0.0,
142 }
143 }
144
145 #[test]
146 fn grid_exact_four_cells_no_jitter() {
147 let positions = pattern_positions(layout_grid(100.0, 100.0, 50.0, 0.0, 0));
152 assert_eq!(positions.len(), 4, "expected 4 cells; got {positions:?}");
153 assert_eq!(positions[0], (0.0, 0.0));
154 assert_eq!(positions[1], (50.0, 0.0));
155 assert_eq!(positions[2], (0.0, 50.0));
156 assert_eq!(positions[3], (50.0, 50.0));
157 }
158
159 #[test]
160 fn grid_missing_spacing_returns_empty() {
161 let p = PatternLayout {
162 kind: "grid",
163 bounds_w: 100.0,
164 bounds_h: 100.0,
165 spacing: None,
166 count: None,
167 seed: 0,
168 jitter: 0.0,
169 };
170 assert!(pattern_positions(p).is_empty());
171 }
172
173 #[test]
174 fn grid_zero_spacing_returns_empty() {
175 let positions = pattern_positions(layout_grid(100.0, 100.0, 0.0, 0.0, 0));
176 assert!(positions.is_empty());
177 }
178
179 #[test]
180 fn scatter_correct_count() {
181 let positions = pattern_positions(layout_scatter(200.0, 150.0, 5, 7));
182 assert_eq!(positions.len(), 5, "expected 5 scatter instances");
183 }
184
185 #[test]
186 fn scatter_within_bounds() {
187 let bw = 300.0;
188 let bh = 200.0;
189 let positions = pattern_positions(layout_scatter(bw, bh, 20, 42));
190 for (ox, oy) in &positions {
191 assert!(*ox >= 0.0 && *ox < bw, "scatter ox={ox} out of [0, {bw})");
192 assert!(*oy >= 0.0 && *oy < bh, "scatter oy={oy} out of [0, {bh})");
193 }
194 }
195
196 #[test]
197 fn scatter_missing_count_returns_empty() {
198 let p = PatternLayout {
199 kind: "scatter",
200 bounds_w: 100.0,
201 bounds_h: 100.0,
202 spacing: None,
203 count: None,
204 seed: 0,
205 jitter: 0.0,
206 };
207 assert!(pattern_positions(p).is_empty());
208 }
209
210 #[test]
211 fn scatter_zero_count_returns_empty() {
212 let positions = pattern_positions(layout_scatter(100.0, 100.0, 0, 0));
213 assert!(positions.is_empty());
214 }
215
216 #[test]
217 fn determinism_grid_same_input_same_output() {
218 let p = layout_grid(120.0, 80.0, 25.0, 0.4, 11);
219 assert_eq!(pattern_positions(p), pattern_positions(p));
220 }
221
222 #[test]
223 fn determinism_scatter_same_input_same_output() {
224 let p = layout_scatter(200.0, 200.0, 7, 99);
225 assert_eq!(pattern_positions(p), pattern_positions(p));
226 }
227
228 #[test]
229 fn unknown_kind_returns_empty() {
230 let p = PatternLayout {
231 kind: "hexagonal",
232 bounds_w: 100.0,
233 bounds_h: 100.0,
234 spacing: Some(20.0),
235 count: Some(10),
236 seed: 0,
237 jitter: 0.0,
238 };
239 assert!(pattern_positions(p).is_empty());
240 }
241}