/* Doom-inspired Q15.16 format used for most of everything. https://hackmd.io/9uRB9YbTSBW4Qz_2b8TyDw#Examples-2-DOOM */ #ifndef COM_FIX_H #define COM_FIX_H #include #define COM_FIX_FRACBITS 16 #define COM_FIX_FRACUNIT (1 << COM_FIX_FRACBITS) #define COM_FIX_FRACHALF (COM_FIX_FRACUNIT >> 1) #define COM_FIX_FRACQRTR (COM_FIX_FRACHALF >> 1) /* Calculated from one number, in hopes that match will converge. */ #define COM_FIX_PI ((com_fix_t)205887) #define COM_FIX_PI2 (COM_FIX_PI << 1) #define COM_FIX_PIHALF (COM_FIX_PI >> 1) #define COM_FIX_PIONEANDAHALF (COM_FIX_PI + COM_FIX_PIHALF) typedef int32_t com_fix_t; extern const uint16_t com_fix_sin_lut[128]; void com_fix_print(com_fix_t a); void com_fix_run_tests(void); void com_fix_run_bench(void); static inline com_fix_t com_fix_add(com_fix_t a, com_fix_t b) { return a + b; } static inline com_fix_t com_fix_sub(com_fix_t a, com_fix_t b) { return a - b; } static inline com_fix_t com_fix_mul(com_fix_t a, com_fix_t b) { return (com_fix_t)(((int64_t)a * (int64_t)b) >> COM_FIX_FRACBITS); } /* Note: this does not clamp for over/underflow cases */ static inline com_fix_t com_fix_div(com_fix_t a, com_fix_t b) { return (com_fix_t)(((int64_t)a << COM_FIX_FRACBITS) / (int64_t)b); } /* Is supposed to be only used for debugging, not in real code. */ /* Because of that we default to maximum precision here. */ static inline double com_fix_as_float(com_fix_t a) { return (a >> COM_FIX_FRACBITS) * (double)1.0 + (a & 0xFFFF) * (double)(1.0 / COM_FIX_FRACUNIT); } /* Branchless floor operation. */ /* TODO: Check against simpler branching implementation. */ static inline com_fix_t com_fix_floor(com_fix_t const a) { int32_t const sign = a >> 31; int32_t const frac_mask = (1 << COM_FIX_FRACBITS) - 1; int32_t const has_fraction = ((a & frac_mask) != 0); int32_t const truncated = a & ~frac_mask; return truncated - ((sign & has_fraction) << COM_FIX_FRACBITS); } /* Approximated square root by Newton-Raphson in 2 iterations over small LUT. */ com_fix_t com_fix_sqrt(com_fix_t a); /* Approximated using LUT. */ /* TODO: test whether linearly interpolated version could be default. */ /* things like camera movement can be jarring if done without.*/ /* https://namoseley.wordpress.com/2015/07/26/sincos-generation-using-table-lookup-and-iterpolation/ */ static inline com_fix_t com_fix_sin(com_fix_t q) { /* Wrap to (-Pi2,+Pi2) */ q %= COM_FIX_PI2; /* Wrap to [0,+Pi2), see: -Pi1/4 = +Pi3/4 */ if (q < 0) q += COM_FIX_PI2; /* Limit to [0,+1) */ q = com_fix_div(q, COM_FIX_PI2); /* Handle cases of 3rd and 4th quadrant separately. */ if (q >= COM_FIX_FRACHALF) { q %= COM_FIX_FRACHALF; uint16_t const idx = q >= COM_FIX_FRACQRTR ? 255 - (q >> 7) : q >> 7; return -com_fix_sin_lut[idx]; } else { uint16_t const idx = q >= COM_FIX_FRACQRTR ? 255 - (q >> 7) : q >> 7; return com_fix_sin_lut[idx]; } } /* Implemented over sin, as to share one single LUT. */ /* Additionally, optimizer probably can collapse some math for combined sincos * case. */ static inline com_fix_t com_fix_cos(com_fix_t a) { return com_fix_sin(a + COM_FIX_PIHALF); } /* Combines calculation of both in a more optimal way. */ static inline void com_fix_sincos(com_fix_t q, com_fix_t *s, com_fix_t *c) { q %= COM_FIX_PI2; if (q < 0) q += COM_FIX_PI2; q = com_fix_div(q, COM_FIX_PI2); /* Sadly, compilers are dumb and branching case is determined to be * significantly faster in profiling. */ #define CASE(m_sin_sign, m_cos_sign) \ uint16_t const idx = q >= COM_FIX_FRACQRTR ? 255 - (q >> 7) : q >> 7; \ *s = m_sin_sign * com_fix_sin_lut[idx]; \ *c = m_cos_sign * com_fix_sin_lut[((uint8_t)127 - (uint8_t)idx) % 128] /* Handle cases of 2rd and 3th quadrant separately, for minus cos. */ if (q >= COM_FIX_FRACHALF / 2 && q < COM_FIX_FRACHALF + COM_FIX_FRACHALF / 2) { /* Handle cases of 3rd and 4th quadrant separately, for minus sin. */ if (q >= COM_FIX_FRACHALF) { q %= COM_FIX_FRACHALF; CASE(-1, -1); } else { CASE(+1, -1); } } else { /* Handle cases of 3rd and 4th quadrant separately, for minus sin. */ if (q >= COM_FIX_FRACHALF) { q %= COM_FIX_FRACHALF; CASE(-1, +1); } else { CASE(+1, +1); } } #undef CASE } static inline com_fix_t com_fix_tan(com_fix_t a) { com_fix_t s, c; com_fix_sincos(a, &s, &c); return com_fix_div(s, c); } /* Branchless min. * https://graphics.stanford.edu/~seander/bithacks.html#IntegerMinOrMax */ static inline com_fix_t com_fix_min(com_fix_t x, com_fix_t y) { return y ^ ((x ^ y) & -(x < y)); } /* Branchless max. * https://graphics.stanford.edu/~seander/bithacks.html#IntegerMinOrMax */ static inline com_fix_t com_fix_max(com_fix_t x, com_fix_t y) { return x ^ ((x ^ y) & -(x < y)); } #endif