Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'source/blender/blenlib')
-rw-r--r--source/blender/blenlib/BLI_index_range.hh4
-rw-r--r--source/blender/blenlib/BLI_listbase_wrapper.hh2
-rw-r--r--source/blender/blenlib/BLI_map.hh4
-rw-r--r--source/blender/blenlib/BLI_math_rotation.h9
-rw-r--r--source/blender/blenlib/BLI_set.hh4
-rw-r--r--source/blender/blenlib/intern/math_rotation.c106
-rw-r--r--source/blender/blenlib/tests/BLI_math_rotation_test.cc103
-rw-r--r--source/blender/blenlib/tests/BLI_set_test.cc10
8 files changed, 202 insertions, 40 deletions
diff --git a/source/blender/blenlib/BLI_index_range.hh b/source/blender/blenlib/BLI_index_range.hh
index 6fcc560d856..2b290e1ba7d 100644
--- a/source/blender/blenlib/BLI_index_range.hh
+++ b/source/blender/blenlib/BLI_index_range.hh
@@ -90,10 +90,10 @@ class IndexRange {
return *this;
}
- constexpr Iterator operator++(int) const
+ constexpr Iterator operator++(int)
{
Iterator copied_iterator = *this;
- ++copied_iterator;
+ ++(*this);
return copied_iterator;
}
diff --git a/source/blender/blenlib/BLI_listbase_wrapper.hh b/source/blender/blenlib/BLI_listbase_wrapper.hh
index 25e029a5616..2d631cb2441 100644
--- a/source/blender/blenlib/BLI_listbase_wrapper.hh
+++ b/source/blender/blenlib/BLI_listbase_wrapper.hh
@@ -50,7 +50,7 @@ template<typename T> class ListBaseWrapper {
Iterator operator++(int)
{
Iterator iterator = *this;
- ++*this;
+ ++(*this);
return iterator;
}
diff --git a/source/blender/blenlib/BLI_map.hh b/source/blender/blenlib/BLI_map.hh
index 55233676ed8..95d1e344894 100644
--- a/source/blender/blenlib/BLI_map.hh
+++ b/source/blender/blenlib/BLI_map.hh
@@ -669,10 +669,10 @@ class Map {
return *this;
}
- BaseIterator operator++(int) const
+ BaseIterator operator++(int)
{
BaseIterator copied_iterator = *this;
- ++copied_iterator;
+ ++(*this);
return copied_iterator;
}
diff --git a/source/blender/blenlib/BLI_math_rotation.h b/source/blender/blenlib/BLI_math_rotation.h
index fef51fa780e..3987c9daf0a 100644
--- a/source/blender/blenlib/BLI_math_rotation.h
+++ b/source/blender/blenlib/BLI_math_rotation.h
@@ -177,10 +177,9 @@ void mat3_to_quat_is_ok(float q[4], const float mat[3][3]);
/* Other. */
/**
- * Utility function that performs `sinf` & `cosf` where the quadrants of the circle
- * will have exactly matching values when their sign is flipped.
- * This works as long as the denominator can be divided by 2 or 4,
- * otherwise `sinf` & `cosf` are used without any additional logic.
+ * Utility that performs `sinf` & `cosf` intended for plotting a 2D circle,
+ * where the values of the coordinates with are exactly symmetrical although this
+ * favors even numbers as odd numbers can only be symmetrical on a single axis.
*
* Besides adjustments to precision, this function is the equivalent of:
* \code {.c}
@@ -194,7 +193,7 @@ void mat3_to_quat_is_ok(float q[4], const float mat[3][3]);
* \param r_sin: The resulting sine.
* \param r_cos: The resulting cosine.
*/
-void sin_cos_from_fraction(const 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);
void print_qt(const char *str, const float q[4]);
diff --git a/source/blender/blenlib/BLI_set.hh b/source/blender/blenlib/BLI_set.hh
index 62de4b79e41..a1b6ad9754e 100644
--- a/source/blender/blenlib/BLI_set.hh
+++ b/source/blender/blenlib/BLI_set.hh
@@ -427,10 +427,10 @@ class Set {
return *this;
}
- Iterator operator++(int) const
+ Iterator operator++(int)
{
Iterator copied_iterator = *this;
- ++copied_iterator;
+ ++(*this);
return copied_iterator;
}
diff --git a/source/blender/blenlib/intern/math_rotation.c b/source/blender/blenlib/intern/math_rotation.c
index f0bfc7c21e1..03275ce19b4 100644
--- a/source/blender/blenlib/intern/math_rotation.c
+++ b/source/blender/blenlib/intern/math_rotation.c
@@ -915,53 +915,107 @@ 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(const int numerator, const int denominator, float *r_sin, float *r_cos)
-{
+void sin_cos_from_fraction(int numerator, const 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).
+ * 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).
+ *
+ * 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) {
+ if (numerator < denominator_4) {
/* Fall through. */
}
else {
- if (numerator <= denominator_4 * 2) {
- const float phi = (float)(2.0 * M_PI) *
- ((float)(numerator - denominator_4) / (float)denominator);
- *r_sin = cosf(phi);
- *r_cos = -sinf(phi);
+ 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) {
- const float phi = (float)(2.0 * M_PI) *
- ((float)(numerator - (denominator_4 * 2)) / (float)denominator);
- *r_sin = -sinf(phi);
- *r_cos = -cosf(phi);
+ else if (numerator == denominator_4 * 3) {
+ numerator = 0;
+ xform = NEGATE_COS | SWAP_SIN_COS;
}
else {
- const float phi = (float)(2.0 * M_PI) *
- ((float)(numerator - (denominator_4 * 3)) / (float)denominator);
- *r_cos = sinf(phi);
- *r_sin = -cosf(phi);
+ numerator -= denominator_4 * 3;
+ xform = NEGATE_COS | SWAP_SIN_COS;
}
- return;
+ }
+ /* 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) {
+ if (numerator < denominator_2) {
/* Fall through. */
}
+ else if (numerator == denominator_2) {
+ numerator = 0;
+ xform = NEGATE_COS;
+ }
else {
- const float phi = (float)(2.0 * M_PI) *
- ((float)(numerator - denominator_2) / (float)denominator);
- *r_sin = -sinf(phi);
- *r_cos = -cosf(phi);
- return;
+ 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;
}
}
const float phi = (float)(2.0 * M_PI) * ((float)numerator / (float)denominator);
- *r_sin = sinf(phi);
- *r_cos = cosf(phi);
+ 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;
+ }
}
void print_qt(const char *str, const float q[4])
diff --git a/source/blender/blenlib/tests/BLI_math_rotation_test.cc b/source/blender/blenlib/tests/BLI_math_rotation_test.cc
index a283118bea2..460cfd2d36c 100644
--- a/source/blender/blenlib/tests/BLI_math_rotation_test.cc
+++ b/source/blender/blenlib/tests/BLI_math_rotation_test.cc
@@ -7,6 +7,8 @@
#include "BLI_math_rotation.hh"
#include "BLI_math_vector.hh"
+#include "BLI_vector.hh"
+
#include <cmath>
/* Test that quaternion converts to itself via matrix. */
@@ -150,6 +152,107 @@ TEST(math_rotation, quat_split_swing_and_twist_negative)
EXPECT_V4_NEAR(twist, expected_twist, FLT_EPSILON);
}
+/* -------------------------------------------------------------------- */
+/** \name Test `sin_cos_from_fraction` Accuracy & Exact Symmetry
+ * \{ */
+
+static void test_sin_cos_from_fraction_accuracy(const int range, const float expected_eps)
+{
+ for (int i = 0; i < range; i++) {
+ float sin_cos_fl[2];
+ sin_cos_from_fraction(i, range, &sin_cos_fl[0], &sin_cos_fl[1]);
+ const float phi = (float)(2.0 * M_PI) * ((float)i / (float)range);
+ const float sin_cos_test_fl[2] = {sinf(phi), cosf(phi)};
+ EXPECT_V2_NEAR(sin_cos_fl, sin_cos_test_fl, expected_eps);
+ }
+}
+
+/** Ensure the result of #sin_cos_from_fraction match #sinf & #cosf. */
+TEST(math_rotation, sin_cos_from_fraction_accuracy)
+{
+ for (int range = 1; range <= 64; range++) {
+ test_sin_cos_from_fraction_accuracy(range, 1e-6f);
+ }
+}
+
+/** Ensure values are exactly symmetrical where possible. */
+static void test_sin_cos_from_fraction_symmetry(const int range)
+{
+ /* The expected number of unique numbers depends on the range being a multiple of 4/2/1. */
+ const enum {
+ MULTIPLE_OF_1 = 1,
+ MULTIPLE_OF_2 = 2,
+ MULTIPLE_OF_4 = 3,
+ } multiple_of = (range & 1) ? MULTIPLE_OF_1 : ((range & 3) ? MULTIPLE_OF_2 : MULTIPLE_OF_4);
+
+ blender::Vector<blender::float2> coords;
+ coords.reserve(range);
+ for (int i = 0; i < range; i++) {
+ float sin_cos_fl[2];
+ sin_cos_from_fraction(i, range, &sin_cos_fl[0], &sin_cos_fl[1]);
+ switch (multiple_of) {
+ case MULTIPLE_OF_1: {
+ sin_cos_fl[0] = fabsf(sin_cos_fl[0]);
+ break;
+ }
+ case MULTIPLE_OF_2: {
+ sin_cos_fl[0] = fabsf(sin_cos_fl[0]);
+ sin_cos_fl[1] = fabsf(sin_cos_fl[1]);
+ break;
+ }
+ case MULTIPLE_OF_4: {
+ sin_cos_fl[0] = fabsf(sin_cos_fl[0]);
+ sin_cos_fl[1] = fabsf(sin_cos_fl[1]);
+ if (sin_cos_fl[0] > sin_cos_fl[1]) {
+ SWAP(float, sin_cos_fl[0], sin_cos_fl[1]);
+ }
+ break;
+ }
+ }
+ coords.append_unchecked(sin_cos_fl);
+ }
+ /* Sort, then count unique items. */
+ std::sort(coords.begin(), coords.end(), [](const blender::float2 &a, const blender::float2 &b) {
+ float delta = b[0] - a[0];
+ if (delta == 0.0f) {
+ delta = b[1] - a[1];
+ }
+ return delta > 0.0f;
+ });
+ int unique_coords_count = 1;
+ if (range > 1) {
+ int i_prev = 0;
+ for (int i = 1; i < range; i_prev = i++) {
+ if (coords[i_prev] != coords[i]) {
+ unique_coords_count += 1;
+ }
+ }
+ }
+ switch (multiple_of) {
+ case MULTIPLE_OF_1: {
+ EXPECT_EQ(unique_coords_count, (range / 2) + 1);
+ break;
+ }
+ case MULTIPLE_OF_2: {
+ EXPECT_EQ(unique_coords_count, (range / 4) + 1);
+ break;
+ }
+ case MULTIPLE_OF_4: {
+ EXPECT_EQ(unique_coords_count, (range / 8) + 1);
+ break;
+ }
+ }
+}
+
+TEST(math_rotation, sin_cos_from_fraction_symmetry)
+{
+ for (int range = 1; range <= 64; range++) {
+ test_sin_cos_from_fraction_symmetry(range);
+ }
+}
+
+/** \} */
+
namespace blender::math::tests {
TEST(math_rotation, RotateDirectionAroundAxis)
diff --git a/source/blender/blenlib/tests/BLI_set_test.cc b/source/blender/blenlib/tests/BLI_set_test.cc
index 5a97b2c7999..9dfa48b5822 100644
--- a/source/blender/blenlib/tests/BLI_set_test.cc
+++ b/source/blender/blenlib/tests/BLI_set_test.cc
@@ -532,8 +532,14 @@ TEST(set, ForwardIterator)
Set<int>::iterator iter1 = set.begin();
int value1 = *iter1;
Set<int>::iterator iter2 = iter1++;
- EXPECT_EQ(*iter1, value1);
- EXPECT_EQ(*iter2, *(++iter1));
+ EXPECT_EQ(*iter2, value1);
+ EXPECT_EQ(*(++iter2), *iter1);
+ /* Interesting find: On GCC & MSVC this will succeed, as the 2nd argument is evaluated before the
+ * 1st. On Apple Clang it's the other way around, and the test fails. */
+ // EXPECT_EQ(*iter1, *(++iter1));
+ Set<int>::iterator iter3 = ++iter1;
+ /* Check that #iter1 itself changed. */
+ EXPECT_EQ(*iter3, *iter1);
}
TEST(set, GenericAlgorithms)