1pub const TURN: i32 = 1 << 16;
21
22#[rustfmt::skip]
27const SIN_QUARTER: [i32; 257] = [
28 0, 402, 804, 1206, 1608, 2010, 2412, 2814,
29 3216, 3617, 4019, 4420, 4821, 5222, 5623, 6023,
30 6424, 6824, 7224, 7623, 8022, 8421, 8820, 9218,
31 9616, 10014, 10411, 10808, 11204, 11600, 11996, 12391,
32 12785, 13180, 13573, 13966, 14359, 14751, 15143, 15534,
33 15924, 16314, 16703, 17091, 17479, 17867, 18253, 18639,
34 19024, 19409, 19792, 20175, 20557, 20939, 21320, 21699,
35 22078, 22457, 22834, 23210, 23586, 23961, 24335, 24708,
36 25080, 25451, 25821, 26190, 26558, 26925, 27291, 27656,
37 28020, 28383, 28745, 29106, 29466, 29824, 30182, 30538,
38 30893, 31248, 31600, 31952, 32303, 32652, 33000, 33347,
39 33692, 34037, 34380, 34721, 35062, 35401, 35738, 36075,
40 36410, 36744, 37076, 37407, 37736, 38064, 38391, 38716,
41 39040, 39362, 39683, 40002, 40320, 40636, 40951, 41264,
42 41576, 41886, 42194, 42501, 42806, 43110, 43412, 43713,
43 44011, 44308, 44604, 44898, 45190, 45480, 45769, 46056,
44 46341, 46624, 46906, 47186, 47464, 47741, 48015, 48288,
45 48559, 48828, 49095, 49361, 49624, 49886, 50146, 50404,
46 50660, 50914, 51166, 51417, 51665, 51911, 52156, 52398,
47 52639, 52878, 53114, 53349, 53581, 53812, 54040, 54267,
48 54491, 54714, 54934, 55152, 55368, 55582, 55794, 56004,
49 56212, 56418, 56621, 56823, 57022, 57219, 57414, 57607,
50 57798, 57986, 58172, 58356, 58538, 58718, 58896, 59071,
51 59244, 59415, 59583, 59750, 59914, 60075, 60235, 60392,
52 60547, 60700, 60851, 60999, 61145, 61288, 61429, 61568,
53 61705, 61839, 61971, 62101, 62228, 62353, 62476, 62596,
54 62714, 62830, 62943, 63054, 63162, 63268, 63372, 63473,
55 63572, 63668, 63763, 63854, 63944, 64031, 64115, 64197,
56 64277, 64354, 64429, 64501, 64571, 64639, 64704, 64766,
57 64827, 64884, 64940, 64993, 65043, 65091, 65137, 65180,
58 65220, 65259, 65294, 65328, 65358, 65387, 65413, 65436,
59 65457, 65476, 65492, 65505, 65516, 65525, 65531, 65535,
60 65536,
61];
62
63fn sin_bam(angle: i32) -> i32 {
65 let a = angle.rem_euclid(TURN);
66 let quarter = TURN / 4;
67 let (quadrant, q) = (a / quarter, a % quarter);
68 let lookup = |q: i32| -> i32 {
71 let idx = (q >> 6) as usize;
72 let frac = q & 63;
73 if frac == 0 {
74 SIN_QUARTER[idx]
75 } else {
76 SIN_QUARTER[idx] + (SIN_QUARTER[idx + 1] - SIN_QUARTER[idx]) * frac / 64
77 }
78 };
79 match quadrant {
80 0 => lookup(q),
81 1 => lookup(quarter - q),
82 2 => -lookup(q),
83 _ => -lookup(quarter - q),
84 }
85}
86
87pub fn direction(angle: i32) -> (i32, i32) {
91 (sin_bam(angle), -sin_bam(angle + TURN / 4))
92}
93
94pub const FRAC_BITS: u32 = 8;
96
97pub const ONE: i32 = 1 << FRAC_BITS;
99
100pub const COORD_LIMIT: i32 = 1 << 22;
104
105#[inline]
107pub fn to_fx(v: i32) -> i32 {
108 v.clamp(-COORD_LIMIT, COORD_LIMIT) << FRAC_BITS
109}
110
111#[cfg(test)]
112mod tests {
113 use super::*;
114
115 #[test]
121 fn every_angle_satisfies_the_pythagorean_identity() {
122 for a in 0..TURN {
123 let s = sin_bam(a) as i64;
124 let c = sin_bam(a + TURN / 4) as i64;
125 let one = (s * s + c * c) >> 16;
126 assert!(
127 (one - 65536).abs() < 64,
128 "angle {a}: sin²+cos² is {one}, not 65536"
129 );
130 }
131 }
132
133 #[test]
136 fn the_cardinal_directions_are_exact() {
137 assert_eq!(direction(0), (0, -65536), "twelve o'clock");
138 assert_eq!(direction(TURN / 4), (65536, 0), "three o'clock");
139 assert_eq!(direction(TURN / 2), (0, 65536), "six o'clock");
140 assert_eq!(direction(3 * TURN / 4), (-65536, 0), "nine o'clock");
141 assert_eq!(direction(TURN), (0, -65536), "and round again");
142 assert_eq!(direction(-TURN / 4), (-65536, 0), "negative wraps too");
143 }
144
145 #[test]
148 fn sine_rises_monotonically_over_the_first_quarter() {
149 let mut previous = -1;
150 for a in 0..=TURN / 4 {
151 let s = sin_bam(a);
152 assert!(s >= previous, "sin fell at angle {a}");
153 previous = s;
154 }
155 }
156}