Effectively, we want to check if a coordinate lies in a 45 degree region (22.5 degrees each side) centered on each compass direction. By first rotating the system by 22.5 degrees clockwise the boundaries to these regions become much easier to work with as they just align with the 8 axes. (Thanks to @Asa-Hopkins for this optimization -- drm)
| 92 | // the boundaries to these regions become much easier to work with as they just align with the 8 axes. |
| 93 | // (Thanks to @Asa-Hopkins for this optimization -- drm) |
| 94 | Dir Coord::asDir() const |
| 95 | { |
| 96 | // tanN/tanD is the best rational approximation to tan(22.5) under the constraint that |
| 97 | // tanN + tanD < 2**16 (to avoid overflows). We don't care about the scale of the result, |
| 98 | // only the ratio of the terms. The actual rotation is (22.5 - 1.5e-8) degrees, whilst |
| 99 | // the closest a pair of int16_t's come to any of these lines is 8e-8 degrees, so the result is exact |
| 100 | constexpr uint16_t tanN = 13860; |
| 101 | constexpr uint16_t tanD = 33461; |
| 102 | const Dir conversion[16] { S, C, SW, N, SE, E, N, |
| 103 | N, N, N, W, NW, N, NE, N, N}; |
| 104 | |
| 105 | const int32_t xp = x * tanD + y * tanN; |
| 106 | const int32_t yp = y * tanD - x * tanN; |
| 107 | |
| 108 | // We can easily check which side of the four boundary lines |
| 109 | // the point now falls on, giving 16 cases, though only 9 are |
| 110 | // possible. |
| 111 | return conversion[(yp > 0) * 8 + (xp > 0) * 4 + (yp > xp) * 2 + (yp >= -xp)]; |
| 112 | } |
| 113 | |
| 114 | |
| 115 | Polar Coord::asPolar() const |
no outgoing calls