1use std::f32;
11use std::vec;
12
13#[derive(Clone, Debug)]
15pub struct JigsawTemplate {
16 pub svg_paths: Vec<String>,
18 pub piece_dimensions: (f32, f32),
20 pub number_of_pieces: (usize, usize),
22}
23
24#[derive(Clone, Debug)]
28struct IndentationSegment {
29 pub starting_point: (f32, f32),
31 pub end_point: (f32, f32),
33 pub control_point_1: (f32, f32),
35 pub control_point_2: (f32, f32),
37}
38
39impl IndentationSegment {
40 pub fn as_path(&self, reverse: bool) -> String {
43 if reverse {
44 format!(
45 "C {},{} {},{} {},{}",
46 &self.control_point_2.0,
47 &self.control_point_2.1,
48 &self.control_point_1.0,
49 &self.control_point_1.1,
50 &self.starting_point.0,
51 &self.starting_point.1
52 )
53 } else {
54 format!(
55 "C {},{} {},{} {},{}",
56 &self.control_point_1.0,
57 &self.control_point_1.1,
58 &self.control_point_2.0,
59 &self.control_point_2.1,
60 &self.end_point.0,
61 &self.end_point.1
62 )
63 }
64 }
65}
66
67#[derive(Clone, Debug)]
68struct IndentedEdge {
71 pub first_segment: IndentationSegment,
73 pub middle_segment: IndentationSegment,
75 pub last_segment: IndentationSegment,
77}
78
79impl IndentedEdge {
80 pub fn new(
82 starting_point: (f32, f32),
83 end_point: (f32, f32),
84 generator: &mut EdgeContourGenerator,
85 ) -> Self {
86 generator.create(starting_point, end_point)
87 }
88 pub fn as_path(&self, reverse: bool) -> String {
91 if reverse {
92 format!(
93 "{} {} {}",
94 &self.last_segment.as_path(reverse),
95 &self.middle_segment.as_path(reverse),
96 &self.first_segment.as_path(reverse)
97 )
98 } else {
99 format!(
100 "{} {} {}",
101 &self.first_segment.as_path(reverse),
102 &self.middle_segment.as_path(reverse),
103 &self.last_segment.as_path(reverse)
104 )
105 }
106 }
107}
108
109struct EdgeContourGenerator {
111 piece_width: f32,
113 piece_height: f32,
115 tab_size: f32,
117 jitter: f32,
119 seed: usize,
121 flipped: bool,
123 a: f32,
125 b: f32,
126 c: f32,
127 d: f32,
128 e: f32,
129}
130
131impl EdgeContourGenerator {
132 pub fn new(
135 piece_width: f32,
136 piece_height: f32,
137 tab_size: Option<f32>,
138 jitter: Option<f32>,
139 seed: Option<usize>,
140 ) -> EdgeContourGenerator {
141 let tab_size = tab_size.unwrap_or(20.0) / 200.0;
142 assert!((0.05..=0.15).contains(&tab_size));
143 let jitter = jitter.unwrap_or(0.0) / 100.0;
144 assert!((0.0..=0.13).contains(&jitter));
145 let seed = seed.unwrap_or(0);
146 let e = Self::uniform(-jitter, jitter, seed + 1);
147 let (seed, flipped, a, b, c, d, e) = Self::dice(e, false, seed + 2, jitter);
148 EdgeContourGenerator {
149 piece_width,
150 piece_height,
151 tab_size,
152 jitter,
153 seed,
154 flipped,
155 a,
156 b,
157 c,
158 d,
159 e,
160 }
161 }
162
163 fn normalise(seed: usize) -> f32 {
165 let x = f32::sin(seed as f32) * 10000.0;
166 x - f32::floor(x)
167 }
168
169 fn uniform(min: f32, max: f32, seed: usize) -> f32 {
171 min + Self::normalise(seed) * (max - min)
172 }
173
174 fn rbool(seed: usize) -> bool {
178 Self::normalise(seed) > 0.5
179 }
180
181 fn dice(
183 e: f32,
184 flipped: bool,
185 seed: usize,
186 jitter: f32,
187 ) -> (usize, bool, f32, f32, f32, f32, f32) {
188 let new_flipped = Self::rbool(seed);
189 let a = if new_flipped == flipped { -e } else { e };
190 let b = Self::uniform(-jitter, jitter, seed + 2);
191 let c = Self::uniform(-jitter, jitter, seed + 3);
192 let d = Self::uniform(-jitter, jitter, seed + 4);
193 let e = Self::uniform(-jitter, jitter, seed + 5);
194 (seed + 6, new_flipped, a, b, c, d, e)
195 }
196
197 fn longitudinal_position(coeff: f32, offset: f32, length: f32) -> f32 {
199 round(offset + coeff * length)
200 }
201
202 fn transverse_position(coeff: f32, offset: f32, length: f32, flipped: bool) -> f32 {
204 round(offset + coeff * length * if flipped { -1.0 } else { 1.0 })
205 }
206
207 fn coords(
212 &self,
213 l_coeff: f32,
214 t_coeff: f32,
215 starting_point: (f32, f32),
216 vertical: bool,
217 ) -> (f32, f32) {
218 let pos_1 = Self::longitudinal_position(
219 l_coeff,
220 if vertical {
221 starting_point.1
222 } else {
223 starting_point.0
224 },
225 if vertical {
226 self.piece_height
227 } else {
228 self.piece_width
229 },
230 );
231 let pos_2 = Self::transverse_position(
232 t_coeff,
233 if vertical {
234 starting_point.0
235 } else {
236 starting_point.1
237 },
238 if vertical {
239 self.piece_width
240 } else {
241 self.piece_height
242 },
243 self.flipped,
244 );
245 if vertical {
246 (pos_2, pos_1)
247 } else {
248 (pos_1, pos_2)
249 }
250 }
251
252 fn ep1(&self, starting_point: (f32, f32), vertical: bool) -> (f32, f32) {
254 self.coords(
255 0.5 - self.tab_size + self.b,
256 self.tab_size + self.c,
257 starting_point,
258 vertical,
259 )
260 }
261
262 fn cp1_1(&self, starting_point: (f32, f32), vertical: bool) -> (f32, f32) {
264 self.coords(0.2, self.a, starting_point, vertical)
265 }
266
267 fn cp1_2(&self, starting_point: (f32, f32), vertical: bool) -> (f32, f32) {
269 self.coords(
270 0.5 + self.b + self.d,
271 -self.tab_size + self.c,
272 starting_point,
273 vertical,
274 )
275 }
276
277 fn ep2(&self, starting_point: (f32, f32), vertical: bool) -> (f32, f32) {
279 self.coords(
280 0.5 + self.tab_size + self.b,
281 self.tab_size + self.c,
282 starting_point,
283 vertical,
284 )
285 }
286
287 fn cp2_1(&self, starting_point: (f32, f32), vertical: bool) -> (f32, f32) {
289 self.coords(
290 0.5 - 2.0 * self.tab_size + self.b - self.d,
291 3.0 * self.tab_size + self.c,
292 starting_point,
293 vertical,
294 )
295 }
296
297 fn cp2_2(&self, starting_point: (f32, f32), vertical: bool) -> (f32, f32) {
299 self.coords(
300 0.5 + 2.0 * self.tab_size + self.b - self.d,
301 3.0 * self.tab_size + self.c,
302 starting_point,
303 vertical,
304 )
305 }
306
307 fn cp3_1(&self, starting_point: (f32, f32), vertical: bool) -> (f32, f32) {
309 self.coords(
310 0.5 + self.b + self.d,
311 -self.tab_size + self.b + self.d,
312 starting_point,
313 vertical,
314 )
315 }
316
317 fn cp3_2(&self, starting_point: (f32, f32), vertical: bool) -> (f32, f32) {
319 self.coords(0.8, self.e, starting_point, vertical)
320 }
321
322 pub fn create(&mut self, starting_point: (f32, f32), end_point: (f32, f32)) -> IndentedEdge {
324 let vertical = (end_point.0 - starting_point.0).abs() < 1.0;
325 let first_segment = IndentationSegment {
326 starting_point,
327 end_point: self.ep1(starting_point, vertical),
328 control_point_1: self.cp1_1(starting_point, vertical),
329 control_point_2: self.cp1_2(starting_point, vertical),
330 };
331 let middle_segment = IndentationSegment {
332 starting_point: self.ep1(starting_point, vertical),
333 end_point: self.ep2(starting_point, vertical),
334 control_point_1: self.cp2_1(starting_point, vertical),
335 control_point_2: self.cp2_2(starting_point, vertical),
336 };
337 let last_segment = IndentationSegment {
338 starting_point: self.ep2(starting_point, vertical),
339 end_point,
340 control_point_1: self.cp3_1(starting_point, vertical),
341 control_point_2: self.cp3_2(starting_point, vertical),
342 };
343 let indented_edge = IndentedEdge {
344 first_segment,
345 middle_segment,
346 last_segment,
347 };
348 (
349 self.seed,
350 self.flipped,
351 self.a,
352 self.b,
353 self.c,
354 self.d,
355 self.e,
356 ) = Self::dice(self.e, false, self.seed + 2, self.jitter);
357 indented_edge
358 }
359}
360
361#[derive(Clone, Debug)]
362struct StraightEdge {
365 pub starting_point: (f32, f32),
366 pub end_point: (f32, f32),
367}
368
369impl StraightEdge {
370 pub fn as_path(&self, reverse: bool) -> String {
373 if reverse {
374 format!("L {},{}", self.starting_point.0, self.starting_point.1)
375 } else {
376 format!("L {},{}", self.end_point.0, self.end_point.1)
377 }
378 }
379}
380
381#[derive(Clone, Debug)]
382enum Edge {
385 IndentedEdge(IndentedEdge),
386 StraightEdge(StraightEdge),
387}
388
389impl Edge {
390 pub fn as_path(&self, reverse: bool) -> String {
393 match self {
394 Edge::IndentedEdge(ie) => ie.as_path(reverse),
395 Edge::StraightEdge(oe) => oe.as_path(reverse),
396 }
397 }
398}
399
400fn divide_axis(length: f32, piece_num: usize) -> (Vec<f32>, f32) {
404 let piece_length = round(length / piece_num as f32);
405 (
406 (0..piece_num)
407 .map(|s| round(s as f32 * piece_length))
408 .collect::<Vec<f32>>(),
409 piece_length,
410 )
411}
412
413pub fn round(x: f32) -> f32 {
415 (x * 100.0).round() / 100.0
416}
417
418fn puzzle_piece(
421 starting_point: (f32, f32),
422 top_edge: &Edge,
423 right_edge: &Edge,
424 bottom_edge: &Edge,
425 left_edge: &Edge,
426) -> String {
427 format!(
428 "M {},{} {} {} {} {} Z",
429 starting_point.0,
430 starting_point.1,
431 top_edge.as_path(false),
432 right_edge.as_path(false),
433 bottom_edge.as_path(true),
434 left_edge.as_path(true)
435 )
436}
437
438fn get_border_indices(position: usize, number_of_columns: usize) -> (usize, usize, usize, usize) {
442 let row_ind = position / number_of_columns;
443 (
444 position,
445 position + 1 + row_ind,
446 position + number_of_columns,
447 position + row_ind,
448 )
449}
450
451fn end_point_pos(ind: usize, segments: &Vec<f32>, fallback: f32) -> f32 {
453 if ind < (segments.len() - 1) {
454 segments[ind + 1]
455 } else {
456 fallback
457 }
458}
459
460fn find_divisors(num: usize) -> Vec<(usize, usize)> {
462 let mut i = 1;
463 let mut divisor_pairs = vec![];
464 loop {
465 if i * i > num {
466 break;
467 } else if num % i == 0 {
468 divisor_pairs.push((i, num / i));
469 }
470 i += 1;
471 }
472 let mut mirrored = divisor_pairs
473 .iter()
474 .filter(|(a, b)| a != b)
475 .map(|(a, b)| (*b, *a))
476 .collect::<Vec<(usize, usize)>>();
477 mirrored.reverse();
478 divisor_pairs.append(&mut mirrored);
479 divisor_pairs
480}
481
482fn optimal_aspect_ratio(
485 possible_dimensions: Vec<(usize, usize)>,
486 image_width: f32,
487 image_height: f32,
488) -> (usize, usize) {
489 let mut width_height_diff = std::f32::MAX;
490 let mut number_of_pieces = *possible_dimensions
491 .first()
492 .expect("No possible dimensions found. This error should never happen!");
493 for (x, y) in possible_dimensions {
494 let width = image_width / x as f32;
495 let height = image_height / y as f32;
496 let new_width_height_diff = (width - height).abs();
497 if new_width_height_diff < 1. {
498 return (x, y);
499 }
500 if width_height_diff >= new_width_height_diff {
501 width_height_diff = new_width_height_diff;
502 number_of_pieces = (x, y);
503 } else {
504 return number_of_pieces;
505 }
506 }
507 number_of_pieces
508}
509
510pub fn generate_columns_rows_numbers(
513 image_width: f32,
514 image_height: f32,
515 number_of_pieces: usize,
516) -> (usize, usize) {
517 let divisor_pairs = find_divisors(number_of_pieces);
518 optimal_aspect_ratio(divisor_pairs, image_width, image_height)
519}
520
521pub fn build_jigsaw_template(
533 image_width: f32,
534 image_height: f32,
535 pieces_in_column: usize,
536 pieces_in_row: usize,
537 tab_size: Option<f32>,
538 jitter: Option<f32>,
539 seed: Option<usize>,
540) -> JigsawTemplate {
541 let (starting_points_x, piece_width) = divide_axis(image_width, pieces_in_column);
542 let (starting_points_y, piece_height) = divide_axis(image_height, pieces_in_row);
543 let mut contour_gen =
544 EdgeContourGenerator::new(piece_width, piece_height, tab_size, jitter, seed);
545 let mut vertical_edges = vec![];
546 let mut horizontal_edges = vec![];
547 let mut top_border = true;
548 for index_y in 0..starting_points_y.len() {
549 let mut left_border = true;
550 for index_x in 0..starting_points_x.len() {
551 horizontal_edges.push(if top_border {
552 Edge::StraightEdge(StraightEdge {
553 starting_point: (starting_points_x[index_x], 0.0),
554 end_point: (end_point_pos(index_x, &starting_points_x, image_width), 0.0),
555 })
556 } else {
557 Edge::IndentedEdge(IndentedEdge::new(
558 (starting_points_x[index_x], starting_points_y[index_y]),
559 (
560 end_point_pos(index_x, &starting_points_x, image_width),
561 starting_points_y[index_y],
562 ),
563 &mut contour_gen,
564 ))
565 });
566 vertical_edges.push(if left_border {
567 Edge::StraightEdge(StraightEdge {
568 starting_point: (0.0, starting_points_y[index_y]),
569 end_point: (
570 0.0,
571 end_point_pos(index_y, &starting_points_y, image_height),
572 ),
573 })
574 } else {
575 Edge::IndentedEdge(IndentedEdge::new(
576 (starting_points_x[index_x], starting_points_y[index_y]),
577 (
578 starting_points_x[index_x],
579 end_point_pos(index_y, &starting_points_y, image_height),
580 ),
581 &mut contour_gen,
582 ))
583 });
584 left_border = false;
585 }
586 top_border = false;
587 vertical_edges.push(Edge::StraightEdge(StraightEdge {
589 starting_point: (image_width, starting_points_y[index_y]),
590 end_point: (
591 image_width,
592 end_point_pos(index_y, &starting_points_y, image_height),
593 ),
594 }));
595 }
596
597 for index_x in 0..starting_points_x.len() {
599 horizontal_edges.push(Edge::StraightEdge(StraightEdge {
600 starting_point: (starting_points_x[index_x], image_height),
601 end_point: (
602 end_point_pos(index_x, &starting_points_x, image_width),
603 image_height,
604 ),
605 }))
606 }
607 let mut svg_paths = vec![];
608 let mut i = 0;
609 for y in starting_points_y.iter() {
610 for x in starting_points_x.iter() {
611 let (top_index, right_index, bottom_index, left_index) =
612 get_border_indices(i, pieces_in_column);
613 svg_paths.push(puzzle_piece(
614 (*x, *y),
615 &horizontal_edges[top_index],
616 &vertical_edges[right_index],
617 &horizontal_edges[bottom_index],
618 &vertical_edges[left_index],
619 ));
620 i += 1;
621 }
622 }
623 JigsawTemplate {
624 svg_paths,
625 piece_dimensions: (piece_width, piece_height),
626 number_of_pieces: (pieces_in_column, pieces_in_row),
627 }
628}
629
630#[cfg(test)]
631mod tests {
632 use super::*;
633
634 #[test]
635 fn test_divide_axis() {
636 let res = divide_axis(1000.0, 4);
637 assert!(res.0.len() == 4);
638 assert!(res.1 > 249.0 && res.1 < 251.0);
639 }
640
641 #[test]
642 fn test_divisor_pairs() {
643 let given_number = 1;
644 assert_eq!(find_divisors(given_number), vec![(1, 1),]);
645
646 let given_number = 24;
647 assert_eq!(
648 find_divisors(given_number),
649 vec![
650 (1, 24),
651 (2, 12),
652 (3, 8),
653 (4, 6),
654 (6, 4),
655 (8, 3),
656 (12, 2),
657 (24, 1),
658 ]
659 );
660
661 let given_number = 9;
662 assert_eq!(find_divisors(given_number), vec![(1, 9), (3, 3), (9, 1),])
663 }
664
665 #[test]
666 fn test_optimal_aspect_ratio() {
667 let image_width: f32 = 1024.;
668 let image_height: f32 = 768.;
669 let possible_aspect_ratios = vec![(1, 25), (5, 5), (25, 1)];
670 assert_eq!(
671 optimal_aspect_ratio(possible_aspect_ratios, image_width, image_height),
672 (5, 5)
673 );
674
675 let image_width: f32 = 666.;
676 let image_height: f32 = 666.;
677 let possible_aspect_ratios = vec![
678 (1, 24),
679 (2, 12),
680 (3, 8),
681 (4, 6),
682 (6, 4),
683 (8, 3),
684 (12, 2),
685 (24, 1),
686 ];
687 assert_eq!(
688 optimal_aspect_ratio(possible_aspect_ratios, image_width, image_height),
689 (6, 4)
690 );
691 }
692}