[Bf-blender-cvs] [77c867a5ee0] master: Cleanup: simplify sin_cos_from_fraction

Chris Blackbourn noreply at git.blender.org
Mon Aug 15 05:48:55 CEST 2022


Commit: 77c867a5ee0141c6115c82f155ce5e1b7a487a66
Author: Chris Blackbourn
Date:   Mon Aug 15 15:39:07 2022 +1200
Branches: master
https://developer.blender.org/rB77c867a5ee0141c6115c82f155ce5e1b7a487a66

Cleanup: simplify sin_cos_from_fraction

Multiply numerator and denominator by 8 to split circle into octants.
Use symmetry and negation to increase precision.

===================================================================

M	source/blender/blenlib/intern/math_rotation.c

===================================================================

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])



More information about the Bf-blender-cvs mailing list