diff options
author | WlodekM <[email protected]> | 2024-06-16 10:35:45 +0300 |
---|---|---|
committer | WlodekM <[email protected]> | 2024-06-16 10:35:45 +0300 |
commit | abef6da56913f1c55528103e60a50451a39628b1 (patch) | |
tree | b3c8092471ecbb73e568cd0d336efa0e7871ee8d /src/ExtMath.c |
initial commit
Diffstat (limited to 'src/ExtMath.c')
-rw-r--r-- | src/ExtMath.c | 468 |
1 files changed, 468 insertions, 0 deletions
diff --git a/src/ExtMath.c b/src/ExtMath.c new file mode 100644 index 0000000..c1409c7 --- /dev/null +++ b/src/ExtMath.c @@ -0,0 +1,468 @@ +#include "ExtMath.h" +#include "Platform.h" +#include "Utils.h" +/* For abs(x) function */ +#include <stdlib.h> + +#define PI 3.141592653589793238462643383279502884197169399 + +/* Sega saturn is missing these intrinsics */ +#ifdef CC_BUILD_SATURN +#include <stdint.h> +extern int32_t fix16_sqrt(int32_t value); +static int abs(int x) { return x < 0 ? -x : x; } + +float sqrtf(float x) { + int32_t fp_x = (int32_t)(x * (1 << 16)); + fp_x = fix16_sqrt(fp_x); + return (float)fp_x / (1 << 16); + } +#endif + + +#if defined CC_BUILD_PS1 + /* PS1 is missing these intrinsics */ + #include <psxgte.h> + float Math_AbsF(float x) { return __builtin_fabsf(x); } + + float Math_SqrtF(float x) { + int fp_x = (int)(x * (1 << 12)); + fp_x = SquareRoot12(fp_x); + return (float)fp_x / (1 << 12); + } +#elif defined __GNUC__ + /* Defined in .h using builtins */ +#else + #include <math.h> + + float Math_AbsF(float x) { return fabsf(x); /* MSVC intrinsic */ } + float Math_SqrtF(float x) { return sqrtf(x); /* MSVC intrinsic */ } +#endif + +float Math_Mod1(float x) { return x - (int)x; /* fmodf(x, 1); */ } +int Math_AbsI(int x) { return abs(x); /* MSVC intrinsic */ } + +int Math_Floor(float value) { + int valueI = (int)value; + return valueI > value ? valueI - 1 : valueI; +} + +int Math_Ceil(float value) { + int valueI = (int)value; + return valueI < value ? valueI + 1 : valueI; +} + +int Math_ilog2(cc_uint32 value) { + cc_uint32 r = 0; + while (value >>= 1) r++; + return r; +} + +int Math_CeilDiv(int a, int b) { + return a / b + (a % b != 0 ? 1 : 0); +} + +int Math_Sign(float value) { + if (value > 0.0f) return +1; + if (value < 0.0f) return -1; + return 0; +} + +float Math_Lerp(float a, float b, float t) { + return a + (b - a) * t; +} + +float Math_ClampAngle(float degrees) { + while (degrees >= 360.0f) degrees -= 360.0f; + while (degrees < 0.0f) degrees += 360.0f; + return degrees; +} + +float Math_LerpAngle(float leftAngle, float rightAngle, float t) { + /* Need to potentially adjust a bit when interpolating some angles */ + /* Consider 350* --> 0*, we only want to interpolate across the 10* */ + /* But without adjusting for this case, we would interpolate back the whole 350* degrees */ + cc_bool invertLeft = leftAngle > 270.0f && rightAngle < 90.0f; + cc_bool invertRight = rightAngle > 270.0f && leftAngle < 90.0f; + if (invertLeft) leftAngle = leftAngle - 360.0f; + if (invertRight) rightAngle = rightAngle - 360.0f; + + return Math_Lerp(leftAngle, rightAngle, t); +} + +int Math_NextPowOf2(int value) { + int next = 1; + while (value > next) { next <<= 1; } + return next; +} + +cc_bool Math_IsPowOf2(int value) { + return value != 0 && (value & (value - 1)) == 0; +} + + +/*########################################################################################################################* +*--------------------------------------------------Random number generator------------------------------------------------* +*#########################################################################################################################*/ +#define RND_VALUE (0x5DEECE66DULL) +#define RND_MASK ((1ULL << 48) - 1) + +void Random_SeedFromCurrentTime(RNGState* rnd) { + cc_uint64 now = Stopwatch_Measure(); + Random_Seed(rnd, (int)now); +} + +void Random_Seed(RNGState* seed, int seedInit) { + *seed = (seedInit ^ RND_VALUE) & RND_MASK; +} + +int Random_Next(RNGState* seed, int n) { + cc_int64 raw; + int bits, val; + + if ((n & -n) == n) { /* i.e., n is a power of 2 */ + *seed = (*seed * RND_VALUE + 0xBLL) & RND_MASK; + raw = (cc_int64)(*seed >> (48 - 31)); + return (int)((n * raw) >> 31); + } + + do { + *seed = (*seed * RND_VALUE + 0xBLL) & RND_MASK; + bits = (int)(*seed >> (48 - 31)); + val = bits % n; + } while (bits - val + (n - 1) < 0); + return val; +} + +float Random_Float(RNGState* seed) { + int raw; + + *seed = (*seed * RND_VALUE + 0xBLL) & RND_MASK; + raw = (int)(*seed >> (48 - 24)); + return raw / ((float)(1 << 24)); +} + + +/*########################################################################################################################* +*--------------------------------------------------Transcendental functions-----------------------------------------------* +*#########################################################################################################################*/ +#ifdef CC_BUILD_DREAMCAST +#include <math.h> + +/* If don't have some code referencing libm, then gldc will fail to link with undefined reference to fabs */ +/* TODO: Properly investigate this issue */ +/* double make_dreamcast_build_compile(void) { fabs(4); } */ + +float Math_SinF(float x) { return sinf(x); } +float Math_CosF(float x) { return cosf(x); } +double Math_Exp2(double x) { return exp2(x); } +double Math_Log2(double x) { return log2(x); } +#else +/***** Caleb's Math functions *****/ + +/* This code implements the math functions sine, cosine, arctangent, the + * exponential function, and the logarithmic function. The code uses techniques + * exclusively described in the book "Computer Approximations" by John Fraser + * Hart (1st Edition). Each function approximates their associated math function + * the same way: + * + * 1. First, the function uses properties of the associated math function to + * reduce the input range to a small finite interval, + * + * 2. Second, the function calculates a polynomial, rational, or similar + * function that approximates the associated math function on that small + * finite interval to the desired accuracy. These polynomial, rational, or + * similar functions were calculated by the authors of "Computer + * Approximations" using the Remez algorithm and exist in the book's + * appendix. + */ + +/* NOTE: NaN/Infinity checking was removed from Cos/Sin functions, */ +/* since ClassiCube does not care about the exact return value */ +/* from the mathematical functions anyways */ + +/* Global constants */ +static const double SQRT2 = 1.4142135623730950488016887242096980785696718753769; +#define DIV_2_PI (1.0 / (2.0 * PI)) + +static const cc_uint64 _DBL_NAN = 0x7FF8000000000000ULL; +#define DBL_NAN *((double*)&_DBL_NAN) +static const cc_uint64 _POS_INF = 0x7FF0000000000000ULL; +#define POS_INF *((double*)&_POS_INF) +static const cc_uint64 _NEG_INF = 0xFFF0000000000000ULL; +#define NEG_INF *((double*)&_NEG_INF) + +/* Calculates the floor of a double. + */ +static double Floord(double x) { + if (x >= 0) + return (double) ((int) x); + return (double) (((int) x) - 1); +} + +/************ + * Math_Sin * + ************/ + +/* Calculates the 5th degree polynomial function SIN 2922 listed in the book's + * appendix. + * + * Associated math function: sin(pi/6 * x) + * Allowed input range: [0, 1] + * Precision: 16.47 + */ +static double SinStage1(double x) { + const static double A[] = { + .52359877559829885532, + -.2392459620393377657e-1, + .32795319441392666e-3, + -.214071970654441e-5, + .815113605169e-8, + -.2020852964e-10, + }; + + double P = A[5]; + double x_2 = x * x; + int i; + + for (i = 4; i >= 0; i--) { + P *= x_2; + P += A[i]; + } + P *= x; + return P; +} + +/* Uses the property + * sin(x) = sin(x/3) * (3 - 4 * (sin(x/3))^2) + * to reduce the input range of sin(x) to [0, pi/6]. + * + * Associated math function: sin(2 * pi * x) + * Allowed input range: [0, 0.25] + */ +static double SinStage2(double x) { + double sin_6 = SinStage1(x * 4.0); + return sin_6 * (3.0 - 4.0 * sin_6 * sin_6); +} + +/* Uses the properties of sine to reduce the input range from [0, 2*pi] to [0, + * pi/2]. + * + * Associated math function: sin(2 * pi * x) + * Allowed input range: [0, 1] + */ +static double SinStage3(double x) { + if (x < 0.25) + return SinStage2(x); + if (x < 0.5) + return SinStage2(0.5 - x); + if (x < 0.75) + return -SinStage2(x - 0.5); + return -SinStage2(1.0 - x); +} + +/* Since sine has a period of 2*pi, this function maps any real number to a + * number from [0, 2*pi]. + * + * Associated math function: sin(x) + * Allowed input range: anything + */ +float Math_SinF(float x) { + double x_div_pi; + + x_div_pi = x * DIV_2_PI; + return (float)SinStage3(x_div_pi - Floord(x_div_pi)); +} + +/************ + * Math_Cos * + ************/ + +/* This function works just like the above sine function, except it shifts the + * input by pi/2, using the property cos(x) = sin(x + pi/2). + * + * Associated math function: cos(x) + * Allowed input range: anything + */ +float Math_CosF(float x) { + double x_div_pi_shifted; + + x_div_pi_shifted = x * DIV_2_PI + 0.25; + return (float)SinStage3(x_div_pi_shifted - Floord(x_div_pi_shifted)); +} + +/************ + * Math_Exp * + ************/ + +/* Calculates the function EXPB 1067 listed in the book's appendix. It is of the + * form + * (Q(x^2) + x*P(x^2)) / (Q(x^2) - x*P(x^2)) + * + * Associated math function: 2^x + * Allowed input range: [-1/2, 1/2] + * Precision: 18.08 + */ +static double Exp2Stage1(double x) { + const double A_P[] = { + .1513906799054338915894328e4, + .20202065651286927227886e2, + .23093347753750233624e-1, + }; + + const double A_Q[] = { + .4368211662727558498496814e4, + .233184211427481623790295e3, + 1.0, + }; + + double x_2 = x * x; + double P, Q; + int i; + + P = A_P[2]; + for (i = 1; i >= 0; i--) { + P *= x_2; + P += A_P[i]; + } + P *= x; + + Q = A_Q[2]; + for (i = 1; i >= 0; i--) { + Q *= x_2; + Q += A_Q[i]; + } + + return (Q + P) / (Q - P); +} + +/* Reduces the range of 2^x to [-1/2, 1/2] by using the property + * 2^x = 2^(integer value) * 2^(fractional part). + * 2^(integer value) can be calculated by directly manipulating the bits of the + * double-precision floating point representation. + * + * Associated math function: 2^x + * Allowed input range: anything + */ +double Math_Exp2(double x) { + int x_int; + union { double d; cc_uint64 i; } doi; + + if (x == POS_INF || x == DBL_NAN) + return x; + if (x == NEG_INF) + return 0.0; + + x_int = (int) x; + + if (x < 0) + x_int--; + + if (x_int < -1022) + return 0.0; + if (x_int > 1023) + return POS_INF; + + doi.i = x_int + 1023; + doi.i <<= 52; + + return doi.d * SQRT2 * Exp2Stage1(x - (double) x_int - 0.5); +} + +/************ + * Math_Log * + ************/ + +/* Calculates the 3rd/3rd degree rational function LOG2 2524 listed in the + * book's appendix. + * + * Associated math function: log_2(x) + * Allowed input range: [0.5, 1] + * Precision: 8.32 + */ +static double Log2Stage1(double x) { + const double A_P[] = { + -.205466671951e1, + -.88626599391e1, + .610585199015e1, + .481147460989e1, + }; + + const double A_Q[] = { + .353553425277, + .454517087629e1, + .642784209029e1, + 1.0, + }; + + double P, Q; + int i; + + P = A_P[3]; + for (i = 2; i >= 0; i--) { + P *= x; + P += A_P[i]; + } + + Q = A_Q[3]; + for (i = 2; i >= 0; i--) { + Q *= x; + Q += A_Q[i]; + } + + return P / Q; +} + +/* Reduces the range of log_2(x) by using the property that + * log_2(x) = (x's exponent part) + log_2(x's mantissa part) + * So, by manipulating the bits of the double-precision floating point number + * one can reduce the range of the logarithm function. + * + * Associated math function: log_2(x) + * Allowed input range: anything + */ +double Math_Log2(double x) { + union { double d; cc_uint64 i; } doi; + int exponent; + + if (x == POS_INF) + return POS_INF; + + if (x == DBL_NAN || x <= 0.0) + return DBL_NAN; + + doi.d = x; + exponent = (doi.i >> 52); + exponent -= 1023; + + doi.i |= (((cc_uint64) 1023) << 52); + doi.i &= ~(((cc_uint64) 1024) << 52); + + return exponent + Log2Stage1(doi.d); +} +#endif + +// Approximation of atan2f using the Remez algorithm +// https://math.stackexchange.com/a/1105038 +float Math_Atan2f(float x, float y) { + if (x == 0) { + if (y > 0) return PI / 2.0f; + if (y < 0) return -PI / 2.0f; + return 0; /* Should probably be NaN */ + } + + float ax = Math_AbsF(x); + float ay = Math_AbsF(y); + + float a = (ax < ay) ? (ax / ay) : (ay / ax); + float s = a * a; + float r = ((-0.0464964749f * s + 0.15931422f) * s - 0.327622764f) * s * a + a; + + if (ay > ax) r = 1.57079637f - r; + if (x < 0) r = 3.14159274f - r; + if (y < 0) r = -r; + return r; +} + +double Math_Sin(double x) { return Math_SinF(x); } +double Math_Cos(double x) { return Math_CosF(x); } \ No newline at end of file |