diff options
author | Chris Blackbourn <chrisbblend@gmail.com> | 2022-08-15 06:39:07 +0300 |
---|---|---|
committer | Chris Blackbourn <chrisbblend@gmail.com> | 2022-08-15 06:47:02 +0300 |
commit | 77c867a5ee0141c6115c82f155ce5e1b7a487a66 (patch) | |
tree | 92a87dec48388f87f6184fd854588109d789d797 /source | |
parent | 4a11c0aabb0d9a01a6e16ab290adb3df4ee1db58 (diff) |
Cleanup: simplify sin_cos_from_fraction
Multiply numerator and denominator by 8 to split circle into octants.
Use symmetry and negation to increase precision.
Diffstat (limited to 'source')
-rw-r--r-- | source/blender/blenlib/intern/math_rotation.c | 144 |
1 files changed, 50 insertions, 94 deletions
diff --git a/source/blender/blenlib/intern/math_rotation.c b/source/blender/blenlib/intern/math_rotation.c index 03275ce19b4..ddfaadced60 100644 --- a/source/blender/blenlib/intern/math_rotation.c +++ b/source/blender/blenlib/intern/math_rotation.c @@ -915,107 +915,63 @@ float tri_to_quat(float q[4], const float a[3], const float b[3], const float c[ return len; } -void sin_cos_from_fraction(int numerator, const int denominator, float *r_sin, float *r_cos) +void sin_cos_from_fraction(int numerator, int denominator, float *r_sin, float *r_cos) { - /* By default, creating an circle from an integer: calling #sinf & #cosf on the fraction doesn't - * create symmetrical values (because of float imprecision). + /* By default, creating a circle from an integer: calling #sinf & #cosf on the fraction doesn't + * create symmetrical values (because floats can't represent Pi exactly). * Resolve this when the rotation is calculated from a fraction by mapping the `numerator` * to lower values so X/Y values for points around a circle are exactly symmetrical, see T87779. * - * - Numbers divisible by 4 are mapped to the lower 8th (8 axis symmetry). - * - Even numbers are mapped to the lower quarter (4 axis symmetry). - * - Odd numbers are mapped to the lower half (1 axis symmetry). + * Multiply both the `numerator` and `denominator` by eight to ensure we can divide the circle + * into 8 octants. For each octant, we then use symmetry and negation to bring the `numerator` + * closer to the origin where precision is highest. * - * Once the values are calculated, the are mapped back to their position in the circle - * using negation & swapping values. */ - - BLI_assert((numerator <= denominator) && (denominator > 0)); - enum { NEGATE_SIN_BIT = 0, NEGATE_COS_BIT = 1, SWAP_SIN_COS_BIT = 2 }; - enum { - NEGATE_SIN = (1 << NEGATE_SIN_BIT), - NEGATE_COS = (1 << NEGATE_COS_BIT), - SWAP_SIN_COS = (1 << SWAP_SIN_COS_BIT), - } xform = 0; - if ((denominator & 3) == 0) { - /* The denominator divides by 4, determine the quadrant then further refine the upper 8th. */ - const int denominator_4 = denominator / 4; - if (numerator < denominator_4) { - /* Fall through. */ - } - else { - if (numerator < denominator_4 * 2) { - numerator -= denominator_4; - xform = NEGATE_SIN | SWAP_SIN_COS; - } - else if (numerator == denominator_4 * 2) { - numerator = 0; - xform = NEGATE_COS; - } - else if (numerator < denominator_4 * 3) { - numerator -= denominator_4 * 2; - xform = NEGATE_SIN | NEGATE_COS; - } - else if (numerator == denominator_4 * 3) { - numerator = 0; - xform = NEGATE_COS | SWAP_SIN_COS; - } - else { - numerator -= denominator_4 * 3; - xform = NEGATE_COS | SWAP_SIN_COS; - } - } - /* Further increase accuracy by using the range of the upper 8th. */ - const int numerator_test = denominator_4 - numerator; - if (numerator_test < numerator) { - numerator = numerator_test; - xform ^= SWAP_SIN_COS; - /* Swap #NEGATE_SIN, #NEGATE_COS flags. */ - xform = (xform & (uint)(~(NEGATE_SIN | NEGATE_COS))) | - (((xform & NEGATE_SIN) >> NEGATE_SIN_BIT) << NEGATE_COS_BIT) | - (((xform & NEGATE_COS) >> NEGATE_COS_BIT) << NEGATE_SIN_BIT); - } - } - else if ((denominator & 1) == 0) { - /* The denominator divides by 2, determine the quadrant then further refine the upper 4th. */ - const int denominator_2 = denominator / 2; - if (numerator < denominator_2) { - /* Fall through. */ - } - else if (numerator == denominator_2) { - numerator = 0; - xform = NEGATE_COS; - } - else { - numerator -= denominator_2; - xform = NEGATE_SIN | NEGATE_COS; - } - /* Further increase accuracy by using the range of the upper 4th. */ - const int numerator_test = denominator_2 - numerator; - if (numerator_test < numerator) { - numerator = numerator_test; - xform ^= NEGATE_COS; - } - } - else { - /* The denominator is an odd number, only refine the upper half. */ - const int numerator_test = denominator - numerator; - if (numerator_test < numerator) { - numerator = numerator_test; - xform ^= NEGATE_SIN; - } + * Cases 2, 4, 5 and 7, use the trigonometric identity sin(-x) == -sin(x). + * Cases 1, 2, 5 and 6, swap the pointers `r_sin` and `r_cos`. + */ + BLI_assert(0 <= numerator); + BLI_assert(numerator <= denominator); + BLI_assert(denominator > 0); + + numerator *= 8; /* Multiply numerator the same as denominator. */ + const int octant = numerator / denominator; /* Determine the octant. */ + denominator *= 8; /* Ensure denominator is a multiple of eight. */ + float cos_sign = 1.0f; /* Either 1.0f or -1.0f. */ + + switch (octant) { + case 0: + /* Primary octant, nothing to do. */ + break; + case 1: + case 2: + numerator = (denominator / 4) - numerator; + SWAP(float *, r_sin, r_cos); + break; + case 3: + case 4: + numerator = (denominator / 2) - numerator; + cos_sign = -1.0f; + break; + case 5: + case 6: + numerator = numerator - (denominator * 3 / 4); + SWAP(float *, r_sin, r_cos); + cos_sign = -1.0f; + break; + case 7: + numerator = numerator - denominator; + break; + default: + BLI_assert_unreachable(); } - const float phi = (float)(2.0 * M_PI) * ((float)numerator / (float)denominator); - const float sin_phi = sinf(phi) * ((xform & NEGATE_SIN) ? -1.0f : 1.0f); - const float cos_phi = cosf(phi) * ((xform & NEGATE_COS) ? -1.0f : 1.0f); - if ((xform & SWAP_SIN_COS) == 0) { - *r_sin = sin_phi; - *r_cos = cos_phi; - } - else { - *r_sin = cos_phi; - *r_cos = sin_phi; - } + BLI_assert(-denominator / 4 <= numerator); /* Numerator may be negative. */ + BLI_assert(numerator <= denominator / 4); + BLI_assert(cos_sign == -1.0f || cos_sign == 1.0f); + + const float angle = (float)(2.0 * M_PI) * ((float)numerator / (float)denominator); + *r_sin = sinf(angle); + *r_cos = cosf(angle) * cos_sign; } void print_qt(const char *str, const float q[4]) |