use std::f32;
use std::vec;
#[derive(Clone, Debug)]
pub struct JigsawTemplate {
pub svg_paths: Vec<String>,
pub piece_dimensions: (f32, f32),
pub number_of_pieces: (usize, usize),
}
#[derive(Clone, Debug)]
struct IndentationSegment {
pub starting_point: (f32, f32),
pub end_point: (f32, f32),
pub control_point_1: (f32, f32),
pub control_point_2: (f32, f32),
}
impl IndentationSegment {
pub fn as_path(&self, reverse: bool) -> String {
if reverse {
format!(
"C {},{} {},{} {},{}",
&self.control_point_2.0,
&self.control_point_2.1,
&self.control_point_1.0,
&self.control_point_1.1,
&self.starting_point.0,
&self.starting_point.1
)
} else {
format!(
"C {},{} {},{} {},{}",
&self.control_point_1.0,
&self.control_point_1.1,
&self.control_point_2.0,
&self.control_point_2.1,
&self.end_point.0,
&self.end_point.1
)
}
}
}
#[derive(Clone, Debug)]
struct IndentedEdge {
pub first_segment: IndentationSegment,
pub middle_segment: IndentationSegment,
pub last_segment: IndentationSegment,
}
impl IndentedEdge {
pub fn new(
starting_point: (f32, f32),
end_point: (f32, f32),
generator: &mut EdgeContourGenerator,
) -> Self {
generator.create(starting_point, end_point)
}
pub fn as_path(&self, reverse: bool) -> String {
if reverse {
format!(
"{} {} {}",
&self.last_segment.as_path(reverse),
&self.middle_segment.as_path(reverse),
&self.first_segment.as_path(reverse)
)
} else {
format!(
"{} {} {}",
&self.first_segment.as_path(reverse),
&self.middle_segment.as_path(reverse),
&self.last_segment.as_path(reverse)
)
}
}
}
struct EdgeContourGenerator {
piece_width: f32,
piece_height: f32,
tab_size: f32,
jitter: f32,
seed: usize,
flipped: bool,
a: f32,
b: f32,
c: f32,
d: f32,
e: f32,
}
impl EdgeContourGenerator {
pub fn new(
piece_width: f32,
piece_height: f32,
tab_size: Option<f32>,
jitter: Option<f32>,
seed: Option<usize>,
) -> EdgeContourGenerator {
let tab_size = tab_size.unwrap_or(20.0) / 200.0;
assert!((0.05..=0.15).contains(&tab_size));
let jitter = jitter.unwrap_or(0.0) / 100.0;
assert!((0.0..=0.13).contains(&jitter));
let seed = seed.unwrap_or(0);
let e = Self::uniform(-jitter, jitter, seed + 1);
let (seed, flipped, a, b, c, d, e) = Self::dice(e, false, seed + 2, jitter);
EdgeContourGenerator {
piece_width,
piece_height,
tab_size,
jitter,
seed,
flipped,
a,
b,
c,
d,
e,
}
}
fn normalise(seed: usize) -> f32 {
let x = f32::sin(seed as f32) * 10000.0;
x - f32::floor(x)
}
fn uniform(min: f32, max: f32, seed: usize) -> f32 {
min + Self::normalise(seed) * (max - min)
}
fn rbool(seed: usize) -> bool {
Self::normalise(seed) > 0.5
}
fn dice(
e: f32,
flipped: bool,
seed: usize,
jitter: f32,
) -> (usize, bool, f32, f32, f32, f32, f32) {
let new_flipped = Self::rbool(seed);
let a = if new_flipped == flipped { -e } else { e };
let b = Self::uniform(-jitter, jitter, seed + 2);
let c = Self::uniform(-jitter, jitter, seed + 3);
let d = Self::uniform(-jitter, jitter, seed + 4);
let e = Self::uniform(-jitter, jitter, seed + 5);
(seed + 6, new_flipped, a, b, c, d, e)
}
fn longitudinal_position(coeff: f32, offset: f32, length: f32) -> f32 {
round(offset + coeff * length)
}
fn transverse_position(coeff: f32, offset: f32, length: f32, flipped: bool) -> f32 {
round(offset + coeff * length * if flipped { -1.0 } else { 1.0 })
}
fn coords(
&self,
l_coeff: f32,
t_coeff: f32,
starting_point: (f32, f32),
vertical: bool,
) -> (f32, f32) {
let pos_1 = Self::longitudinal_position(
l_coeff,
if vertical {
starting_point.1
} else {
starting_point.0
},
if vertical {
self.piece_height
} else {
self.piece_width
},
);
let pos_2 = Self::transverse_position(
t_coeff,
if vertical {
starting_point.0
} else {
starting_point.1
},
if vertical {
self.piece_width
} else {
self.piece_height
},
self.flipped,
);
if vertical {
(pos_2, pos_1)
} else {
(pos_1, pos_2)
}
}
fn ep1(&self, starting_point: (f32, f32), vertical: bool) -> (f32, f32) {
self.coords(
0.5 - self.tab_size + self.b,
self.tab_size + self.c,
starting_point,
vertical,
)
}
fn cp1_1(&self, starting_point: (f32, f32), vertical: bool) -> (f32, f32) {
self.coords(0.2, self.a, starting_point, vertical)
}
fn cp1_2(&self, starting_point: (f32, f32), vertical: bool) -> (f32, f32) {
self.coords(
0.5 + self.b + self.d,
-self.tab_size + self.c,
starting_point,
vertical,
)
}
fn ep2(&self, starting_point: (f32, f32), vertical: bool) -> (f32, f32) {
self.coords(
0.5 + self.tab_size + self.b,
self.tab_size + self.c,
starting_point,
vertical,
)
}
fn cp2_1(&self, starting_point: (f32, f32), vertical: bool) -> (f32, f32) {
self.coords(
0.5 - 2.0 * self.tab_size + self.b - self.d,
3.0 * self.tab_size + self.c,
starting_point,
vertical,
)
}
fn cp2_2(&self, starting_point: (f32, f32), vertical: bool) -> (f32, f32) {
self.coords(
0.5 + 2.0 * self.tab_size + self.b - self.d,
3.0 * self.tab_size + self.c,
starting_point,
vertical,
)
}
fn cp3_1(&self, starting_point: (f32, f32), vertical: bool) -> (f32, f32) {
self.coords(
0.5 + self.b + self.d,
-self.tab_size + self.b + self.d,
starting_point,
vertical,
)
}
fn cp3_2(&self, starting_point: (f32, f32), vertical: bool) -> (f32, f32) {
self.coords(0.8, self.e, starting_point, vertical)
}
pub fn create(&mut self, starting_point: (f32, f32), end_point: (f32, f32)) -> IndentedEdge {
let vertical = (end_point.0 - starting_point.0).abs() < 1.0;
let first_segment = IndentationSegment {
starting_point,
end_point: self.ep1(starting_point, vertical),
control_point_1: self.cp1_1(starting_point, vertical),
control_point_2: self.cp1_2(starting_point, vertical),
};
let middle_segment = IndentationSegment {
starting_point: self.ep1(starting_point, vertical),
end_point: self.ep2(starting_point, vertical),
control_point_1: self.cp2_1(starting_point, vertical),
control_point_2: self.cp2_2(starting_point, vertical),
};
let last_segment = IndentationSegment {
starting_point: self.ep2(starting_point, vertical),
end_point,
control_point_1: self.cp3_1(starting_point, vertical),
control_point_2: self.cp3_2(starting_point, vertical),
};
let indented_edge = IndentedEdge {
first_segment,
middle_segment,
last_segment,
};
(
self.seed,
self.flipped,
self.a,
self.b,
self.c,
self.d,
self.e,
) = Self::dice(self.e, false, self.seed + 2, self.jitter);
indented_edge
}
}
#[derive(Clone, Debug)]
struct StraightEdge {
pub starting_point: (f32, f32),
pub end_point: (f32, f32),
}
impl StraightEdge {
pub fn as_path(&self, reverse: bool) -> String {
if reverse {
format!("L {},{}", self.starting_point.0, self.starting_point.1)
} else {
format!("L {},{}", self.end_point.0, self.end_point.1)
}
}
}
#[derive(Clone, Debug)]
enum Edge {
IndentedEdge(IndentedEdge),
StraightEdge(StraightEdge),
}
impl Edge {
pub fn as_path(&self, reverse: bool) -> String {
match self {
Edge::IndentedEdge(ie) => ie.as_path(reverse),
Edge::StraightEdge(oe) => oe.as_path(reverse),
}
}
}
fn divide_axis(length: f32, piece_num: usize) -> (Vec<f32>, f32) {
let piece_length = round(length / piece_num as f32);
(
(0..piece_num)
.map(|s| round(s as f32 * piece_length))
.collect::<Vec<f32>>(),
piece_length,
)
}
pub fn round(x: f32) -> f32 {
(x * 100.0).round() / 100.0
}
fn puzzle_piece(
starting_point: (f32, f32),
top_edge: &Edge,
right_edge: &Edge,
bottom_edge: &Edge,
left_edge: &Edge,
) -> String {
format!(
"M {},{} {} {} {} {} Z",
starting_point.0,
starting_point.1,
top_edge.as_path(false),
right_edge.as_path(false),
bottom_edge.as_path(true),
left_edge.as_path(true)
)
}
fn get_border_indices(position: usize, number_of_columns: usize) -> (usize, usize, usize, usize) {
let row_ind = position / number_of_columns;
(
position,
position + 1 + row_ind,
position + number_of_columns,
position + row_ind,
)
}
fn end_point_pos(ind: usize, segments: &Vec<f32>, fallback: f32) -> f32 {
if ind < (segments.len() - 1) {
segments[ind + 1]
} else {
fallback
}
}
fn find_divisors(num: usize) -> Vec<(usize, usize)> {
let mut i = 1;
let mut divisor_pairs = vec![];
loop {
if i * i > num {
break;
} else if num % i == 0 {
divisor_pairs.push((i, num / i));
}
i += 1;
}
let mut mirrored = divisor_pairs
.iter()
.filter(|(a, b)| a != b)
.map(|(a, b)| (*b, *a))
.collect::<Vec<(usize, usize)>>();
mirrored.reverse();
divisor_pairs.append(&mut mirrored);
divisor_pairs
}
fn optimal_aspect_ratio(
possible_dimensions: Vec<(usize, usize)>,
image_width: f32,
image_height: f32,
) -> (usize, usize) {
let mut width_height_diff = std::f32::MAX;
let mut number_of_pieces = *possible_dimensions
.first()
.expect("No possible dimensions found. This error should never happen!");
for (x, y) in possible_dimensions {
let width = image_width / x as f32;
let height = image_height / y as f32;
let new_width_height_diff = (width - height).abs();
if new_width_height_diff < 1. {
return (x, y);
}
if width_height_diff >= new_width_height_diff {
width_height_diff = new_width_height_diff;
number_of_pieces = (x, y);
} else {
return number_of_pieces;
}
}
number_of_pieces
}
pub fn generate_columns_rows_numbers(
image_width: f32,
image_height: f32,
number_of_pieces: usize,
) -> (usize, usize) {
let divisor_pairs = find_divisors(number_of_pieces);
optimal_aspect_ratio(divisor_pairs, image_width, image_height)
}
pub fn build_jigsaw_template(
image_width: f32,
image_height: f32,
pieces_in_column: usize,
pieces_in_row: usize,
tab_size: Option<f32>,
jitter: Option<f32>,
seed: Option<usize>,
) -> JigsawTemplate {
let (starting_points_x, piece_width) = divide_axis(image_width, pieces_in_column);
let (starting_points_y, piece_height) = divide_axis(image_height, pieces_in_row);
let mut contour_gen =
EdgeContourGenerator::new(piece_width, piece_height, tab_size, jitter, seed);
let mut vertical_edges = vec![];
let mut horizontal_edges = vec![];
let mut top_border = true;
for index_y in 0..starting_points_y.len() {
let mut left_border = true;
for index_x in 0..starting_points_x.len() {
horizontal_edges.push(if top_border {
Edge::StraightEdge(StraightEdge {
starting_point: (starting_points_x[index_x], 0.0),
end_point: (end_point_pos(index_x, &starting_points_x, image_width), 0.0),
})
} else {
Edge::IndentedEdge(IndentedEdge::new(
(starting_points_x[index_x], starting_points_y[index_y]),
(
end_point_pos(index_x, &starting_points_x, image_width),
starting_points_y[index_y],
),
&mut contour_gen,
))
});
vertical_edges.push(if left_border {
Edge::StraightEdge(StraightEdge {
starting_point: (0.0, starting_points_y[index_y]),
end_point: (
0.0,
end_point_pos(index_y, &starting_points_y, image_height),
),
})
} else {
Edge::IndentedEdge(IndentedEdge::new(
(starting_points_x[index_x], starting_points_y[index_y]),
(
starting_points_x[index_x],
end_point_pos(index_y, &starting_points_y, image_height),
),
&mut contour_gen,
))
});
left_border = false;
}
top_border = false;
vertical_edges.push(Edge::StraightEdge(StraightEdge {
starting_point: (image_width, starting_points_y[index_y]),
end_point: (
image_width,
end_point_pos(index_y, &starting_points_y, image_height),
),
}));
}
for index_x in 0..starting_points_x.len() {
horizontal_edges.push(Edge::StraightEdge(StraightEdge {
starting_point: (starting_points_x[index_x], image_height),
end_point: (
end_point_pos(index_x, &starting_points_x, image_width),
image_height,
),
}))
}
let mut svg_paths = vec![];
let mut i = 0;
for y in starting_points_y.iter() {
for x in starting_points_x.iter() {
let (top_index, right_index, bottom_index, left_index) =
get_border_indices(i, pieces_in_column);
svg_paths.push(puzzle_piece(
(*x, *y),
&horizontal_edges[top_index],
&vertical_edges[right_index],
&horizontal_edges[bottom_index],
&vertical_edges[left_index],
));
i += 1;
}
}
JigsawTemplate {
svg_paths,
piece_dimensions: (piece_width, piece_height),
number_of_pieces: (pieces_in_column, pieces_in_row),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_divide_axis() {
let res = divide_axis(1000.0, 4);
assert!(res.0.len() == 4);
assert!(res.1 > 249.0 && res.1 < 251.0);
}
#[test]
fn test_divisor_pairs() {
let given_number = 1;
assert_eq!(find_divisors(given_number), vec![(1, 1),]);
let given_number = 24;
assert_eq!(
find_divisors(given_number),
vec![
(1, 24),
(2, 12),
(3, 8),
(4, 6),
(6, 4),
(8, 3),
(12, 2),
(24, 1),
]
);
let given_number = 9;
assert_eq!(find_divisors(given_number), vec![(1, 9), (3, 3), (9, 1),])
}
#[test]
fn test_optimal_aspect_ratio() {
let image_width: f32 = 1024.;
let image_height: f32 = 768.;
let possible_aspect_ratios = vec![(1, 25), (5, 5), (25, 1)];
assert_eq!(
optimal_aspect_ratio(possible_aspect_ratios, image_width, image_height),
(5, 5)
);
let image_width: f32 = 666.;
let image_height: f32 = 666.;
let possible_aspect_ratios = vec![
(1, 24),
(2, 12),
(3, 8),
(4, 6),
(6, 4),
(8, 3),
(12, 2),
(24, 1),
];
assert_eq!(
optimal_aspect_ratio(possible_aspect_ratios, image_width, image_height),
(6, 4)
);
}
}