fixed to fix and some more
This commit is contained in:
+142
@@ -0,0 +1,142 @@
|
||||
/*
|
||||
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 <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#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);
|
||||
}
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user