1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
use std::f64::consts::PI;
pub fn first_angle(size: usize, incoming: f64) -> f64 {
incoming + 90.0 - 180.0 / size as f64
}
pub fn turn(size: usize) -> f64 {
-360.0 / size as f64
}
pub fn radius(size: usize, span: f64) -> f64 {
span / (2.0 * (PI / size as f64).sin())
}
pub fn inradius(size: usize, span: f64) -> f64 {
span / (2.0 * (PI / size as f64).tan())
}
pub fn center(size: usize, span: f64, vertex: (f64, f64), incoming: f64) -> (f64, f64) {
let length = radius(size, span);
let radian = incoming.to_radians();
(vertex.0 + radian.cos() * length, vertex.1 - radian.sin() * length)
}
pub fn inset(size: usize, gap: f64) -> f64 {
let half = (180.0 - 360.0 / size as f64) / 2.0;
gap / half.to_radians().tan()
}
#[cfg(test)]
mod tests {
use super::*;
fn walk(size: usize, span: f64, incoming: f64) -> Vec<(f64, f64)> {
let mut found = vec![(0.0, 0.0)];
let mut angle = first_angle(size, incoming);
for _ in 0..size {
let last = *found.last().unwrap();
let radian = angle.to_radians();
found.push((last.0 + radian.cos() * span, last.1 - radian.sin() * span));
angle += turn(size);
}
found
}
#[test]
fn a_ring_comes_back_to_where_it_started() {
for size in 3..=12 {
let found = walk(size, 3.0, 0.0);
let last = found[size];
assert!(last.0.abs() < 1e-9 && last.1.abs() < 1e-9, "the ring of size {size} is open");
}
}
#[test]
fn every_side_has_the_same_length() {
let found = walk(6, 3.0, 0.0);
for pair in found.windows(2) {
let span = ((pair[1].0 - pair[0].0).powi(2) + (pair[1].1 - pair[0].1).powi(2)).sqrt();
assert!((span - 3.0).abs() < 1e-9);
}
}
#[test]
fn a_plain_hexagon_lies_flat() {
let found = walk(6, 3.0, 0.0);
assert!((found[1].0 - 1.5).abs() < 1e-9);
assert!((found[1].1 + 2.598).abs() < 1e-3);
assert!((found[3].0 - 6.0).abs() < 1e-9);
assert!(found[3].1.abs() < 1e-9, "opposite vertices are not level");
}
#[test]
fn the_ring_grows_along_the_bond_that_came_in() {
let middle = center(6, 3.0, (0.0, 0.0), 0.0);
assert!((middle.0 - 3.0).abs() < 1e-9);
assert!(middle.1.abs() < 1e-9);
let upward = center(6, 3.0, (0.0, 0.0), 90.0);
assert!(upward.0.abs() < 1e-9);
assert!((upward.1 + 3.0).abs() < 1e-9);
}
#[test]
fn the_center_is_the_same_distance_from_every_vertex() {
let found = walk(5, 3.0, 30.0);
let middle = center(5, 3.0, (0.0, 0.0), 30.0);
let length = radius(5, 3.0);
for vertex in &found[0..5] {
let span = ((vertex.0 - middle.0).powi(2) + (vertex.1 - middle.1).powi(2)).sqrt();
assert!((span - length).abs() < 1e-9);
}
}
#[test]
fn the_inner_radius_is_shorter_than_the_outer() {
for size in 3..=12 {
assert!(inradius(size, 3.0) < radius(size, 3.0));
}
}
#[test]
fn a_hexagon_trims_its_inner_line_by_the_gap_over_root_three() {
assert!((inset(6, 0.2) - 0.2 / 3f64.sqrt()).abs() < 1e-9);
}
}