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
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
//! Geometry of rings
//!
//! A ring is placed as a regular polygon. The first vertex is the atom that holds the ring.
//! From there, the vertices are found by following the sides one at a time, clockwise.
//!
//! The direction of the first side is set by the direction of the incoming bond. The ring is
//! placed so that the incoming bond bisects the exterior angle at the first vertex, so the ring
//! extends straight on in the direction of the incoming bond.
//! If no bond comes in, the ring is treated as if a bond came in horizontally.
use std::f64::consts::PI;
/// Direction of the first side (in degrees)
pub fn first_angle(size: usize, incoming: f64) -> f64 {
incoming + 90.0 - 180.0 / size as f64
}
/// How far the direction turns at each side (in degrees). Negative because it turns clockwise
pub fn turn(size: usize) -> f64 {
-360.0 / size as f64
}
/// Distance from a vertex to the center
pub fn radius(size: usize, span: f64) -> f64 {
span / (2.0 * (PI / size as f64).sin())
}
/// Distance from a side to the center
pub fn inradius(size: usize, span: f64) -> f64 {
span / (2.0 * (PI / size as f64).tan())
}
/// Position of the center, worked out from the first vertex
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)
}
/// How much to trim both ends of a double bond that is shifted toward the inside
///
/// When trimmed, its ends meet the inner lines of the neighboring sides.
/// Without trimming, it extends past the corners
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::*;
/// Follows the ring and lists the vertices. Written only for the tests
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, "{size} 員環が閉じていません");
}
}
#[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);
// The first side is at 60 degrees, so the top and bottom sides are horizontal
// and the shape is pointed at the left and right
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, "向かいの頂点が同じ高さにありません");
}
#[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);
}
}