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_cache_mutex.hh106
-rw-r--r--source/blender/blenlib/BLI_lazy_threading.hh11
-rw-r--r--source/blender/blenlib/BLI_string.h8
-rw-r--r--source/blender/blenlib/BLI_task.hh1
-rw-r--r--source/blender/blenlib/CMakeLists.txt2
-rw-r--r--source/blender/blenlib/intern/cache_mutex.cc25
-rw-r--r--source/blender/blenlib/intern/lazy_threading.cc30
-rw-r--r--source/blender/blenlib/intern/math_rotation.c101
-rw-r--r--source/blender/blenlib/intern/path_util.c4
-rw-r--r--source/blender/blenlib/intern/string.c15
-rw-r--r--source/blender/blenlib/intern/task_scheduler.cc2
-rw-r--r--source/blender/blenlib/intern/uuid.cc27
-rw-r--r--source/blender/blenlib/intern/winstuff.c14
-rw-r--r--source/blender/blenlib/tests/BLI_math_rotation_test.cc16
14 files changed, 293 insertions, 69 deletions
diff --git a/source/blender/blenlib/BLI_cache_mutex.hh b/source/blender/blenlib/BLI_cache_mutex.hh
new file mode 100644
index 00000000000..8e2a0d1b1a5
--- /dev/null
+++ b/source/blender/blenlib/BLI_cache_mutex.hh
@@ -0,0 +1,106 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#pragma once
+
+/**
+ * A #CacheMutex is used to protect a lazily computed cache from being computed more than once.
+ * Using #CacheMutex instead of a "raw mutex" to protect a cache has some benefits:
+ * - Avoid common pitfalls like forgetting to use task isolation or a double checked lock.
+ * - Cleaner and less redundant code because the same locking patterns don't have to be repeated
+ * everywhere.
+ * - One can benefit from potential future improvements to #CacheMutex of which there are a few
+ * mentioned below.
+ *
+ * The data protected by #CacheMutex is not part of #CacheMutex. Instead, the #CacheMutex and its
+ * protected data should generally be placed next to each other.
+ *
+ * Each #CacheMutex protects exactly one cache, so multiple cache mutexes have to be used when a
+ * class has multiple caches. That is contrary to a "custom" solution using `std::mutex` where one
+ * mutex could protect multiple caches at the cost of higher lock contention.
+ *
+ * To make sure the cache is up to date, call `CacheMutex::ensure` and pass in the function that
+ * computes the cache.
+ *
+ * To tell the #CacheMutex that the cache is invalidated and to be re-evaluated upon next access
+ * use `CacheMutex::tag_dirty`.
+ *
+ * This example shows how one could implement a lazily computed average vertex position in an
+ * imaginary `Mesh` data structure:
+ *
+ * \code{.cpp}
+ * class Mesh {
+ * private:
+ * mutable CacheMutex average_position_cache_mutex_;
+ * mutable float3 average_position_cache_;
+ *
+ * public:
+ * const float3 &average_position() const
+ * {
+ * average_position_cache_mutex_.ensure([&]() {
+ * average_position_cache_ = actually_compute_average_position();
+ * });
+ * return average_position_cache_;
+ * }
+ *
+ * void tag_positions_changed()
+ * {
+ * average_position_cache_mutex_.tag_dirty();
+ * }
+ * };
+ * \endcode
+ *
+ * Possible future improvements:
+ * - Avoid task isolation when we know that the cache computation does not use threading.
+ * - Try to use a smaller mutex. The mutex does not have to be fair for this use case.
+ * - Try to join the cache computation instead of blocking if another thread is computing the cache
+ * already.
+ */
+
+#include <atomic>
+#include <mutex>
+
+#include "BLI_function_ref.hh"
+
+namespace blender {
+
+class CacheMutex {
+ private:
+ std::mutex mutex_;
+ std::atomic<bool> cache_valid_ = false;
+
+ public:
+ /**
+ * Make sure the cache exists and is up to date. This calls `compute_cache` once to update the
+ * cache (which is stored outside of this class) if it is dirty, otherwise it does nothing.
+ *
+ * This function is thread-safe under the assumption that the same parameters are passed from
+ * every thread.
+ */
+ void ensure(FunctionRef<void()> compute_cache);
+
+ /**
+ * Reset the cache. The next time #ensure is called, it will recompute that code.
+ */
+ void tag_dirty()
+ {
+ cache_valid_.store(false);
+ }
+
+ /**
+ * Return true if the cache currently does not exist or has been invalidated.
+ */
+ bool is_dirty() const
+ {
+ return !this->is_cached();
+ }
+
+ /**
+ * Return true if the cache exists and is valid.
+ */
+ bool is_cached() const
+ {
+ return cache_valid_.load(std::memory_order_relaxed);
+ }
+};
+
+} // namespace blender
diff --git a/source/blender/blenlib/BLI_lazy_threading.hh b/source/blender/blenlib/BLI_lazy_threading.hh
index b5a15919c89..4d04fe9e908 100644
--- a/source/blender/blenlib/BLI_lazy_threading.hh
+++ b/source/blender/blenlib/BLI_lazy_threading.hh
@@ -80,4 +80,15 @@ class HintReceiver {
~HintReceiver();
};
+/**
+ * Used to make sure that lazy-threading hints don't propagate through task isolation. This is
+ * necessary to avoid deadlocks when isolated regions are used together with e.g. task pools. For
+ * more info see the comment on #BLI_task_isolate.
+ */
+class ReceiverIsolation {
+ public:
+ ReceiverIsolation();
+ ~ReceiverIsolation();
+};
+
} // namespace blender::lazy_threading
diff --git a/source/blender/blenlib/BLI_string.h b/source/blender/blenlib/BLI_string.h
index 17abcf52ecc..ed15e0871b9 100644
--- a/source/blender/blenlib/BLI_string.h
+++ b/source/blender/blenlib/BLI_string.h
@@ -206,6 +206,14 @@ char *BLI_sprintfN(const char *__restrict format, ...) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL(1) ATTR_MALLOC ATTR_PRINTF_FORMAT(1, 2);
/**
+ * A wrapper around `::sprintf()` which does not generate security warnings.
+ *
+ * \note Use #BLI_snprintf for cases when the string size is known.
+ */
+int BLI_sprintf(char *__restrict str, const char *__restrict format, ...) ATTR_NONNULL(1, 2)
+ ATTR_PRINTF_FORMAT(2, 3);
+
+/**
* This roughly matches C and Python's string escaping with double quotes - `"`.
*
* Since every character may need escaping,
diff --git a/source/blender/blenlib/BLI_task.hh b/source/blender/blenlib/BLI_task.hh
index 9f9a57be634..e7d9a21439a 100644
--- a/source/blender/blenlib/BLI_task.hh
+++ b/source/blender/blenlib/BLI_task.hh
@@ -129,6 +129,7 @@ void parallel_invoke(const bool use_threading, Functions &&...functions)
template<typename Function> void isolate_task(const Function &function)
{
#ifdef WITH_TBB
+ lazy_threading::ReceiverIsolation isolation;
tbb::this_task_arena::isolate(function);
#else
function();
diff --git a/source/blender/blenlib/CMakeLists.txt b/source/blender/blenlib/CMakeLists.txt
index 2ac77f000e9..693a4d98675 100644
--- a/source/blender/blenlib/CMakeLists.txt
+++ b/source/blender/blenlib/CMakeLists.txt
@@ -54,6 +54,7 @@ set(SRC
intern/bitmap_draw_2d.c
intern/boxpack_2d.c
intern/buffer.c
+ intern/cache_mutex.cc
intern/compute_context.cc
intern/convexhull_2d.c
intern/cpp_type.cc
@@ -178,6 +179,7 @@ set(SRC
BLI_bounds.hh
BLI_boxpack_2d.h
BLI_buffer.h
+ BLI_cache_mutex.hh
BLI_color.hh
BLI_color_mix.hh
BLI_compiler_attrs.h
diff --git a/source/blender/blenlib/intern/cache_mutex.cc b/source/blender/blenlib/intern/cache_mutex.cc
new file mode 100644
index 00000000000..db474b1ef87
--- /dev/null
+++ b/source/blender/blenlib/intern/cache_mutex.cc
@@ -0,0 +1,25 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#include "BLI_cache_mutex.hh"
+#include "BLI_task.hh"
+
+namespace blender {
+
+void CacheMutex::ensure(const FunctionRef<void()> compute_cache)
+{
+ if (cache_valid_.load(std::memory_order_acquire)) {
+ return;
+ }
+ std::scoped_lock lock{mutex_};
+ /* Double checked lock. */
+ if (cache_valid_.load(std::memory_order_relaxed)) {
+ return;
+ }
+ /* Use task isolation because a mutex is locked and the cache computation might use
+ * multi-threading. */
+ threading::isolate_task(compute_cache);
+
+ cache_valid_.store(true, std::memory_order_release);
+}
+
+} // namespace blender
diff --git a/source/blender/blenlib/intern/lazy_threading.cc b/source/blender/blenlib/intern/lazy_threading.cc
index 803fd81a96d..4f6d3a75ecc 100644
--- a/source/blender/blenlib/intern/lazy_threading.cc
+++ b/source/blender/blenlib/intern/lazy_threading.cc
@@ -1,30 +1,50 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BLI_lazy_threading.hh"
+#include "BLI_stack.hh"
#include "BLI_vector.hh"
namespace blender::lazy_threading {
/**
- * This is a #RawVector so that it can be destructed after Blender checks for memory leaks.
+ * This uses a "raw" stack and vector so that it can be destructed after Blender checks for memory
+ * leaks. A new list of receivers is created whenever an isolated region is entered to avoid
+ * deadlocks.
*/
-thread_local RawVector<FunctionRef<void()>, 0> hint_receivers;
+using HintReceivers = RawStack<RawVector<FunctionRef<void()>, 0>, 0>;
+thread_local HintReceivers hint_receivers = []() {
+ HintReceivers receivers;
+ /* Make sure there is always at least one vector. */
+ receivers.push_as();
+ return receivers;
+}();
void send_hint()
{
- for (const FunctionRef<void()> &fn : hint_receivers) {
+ for (const FunctionRef<void()> &fn : hint_receivers.peek()) {
fn();
}
}
HintReceiver::HintReceiver(const FunctionRef<void()> fn)
{
- hint_receivers.append(fn);
+ hint_receivers.peek().append(fn);
}
HintReceiver::~HintReceiver()
{
- hint_receivers.pop_last();
+ hint_receivers.peek().pop_last();
+}
+
+ReceiverIsolation::ReceiverIsolation()
+{
+ hint_receivers.push_as();
+}
+
+ReceiverIsolation::~ReceiverIsolation()
+{
+ BLI_assert(hint_receivers.peek().is_empty());
+ hint_receivers.pop();
}
} // namespace blender::lazy_threading
diff --git a/source/blender/blenlib/intern/math_rotation.c b/source/blender/blenlib/intern/math_rotation.c
index ff45bbee5c9..180412c4a14 100644
--- a/source/blender/blenlib/intern/math_rotation.c
+++ b/source/blender/blenlib/intern/math_rotation.c
@@ -275,64 +275,83 @@ void mat3_normalized_to_quat_fast(float q[4], const float mat[3][3])
/* Caller must ensure matrices aren't negative for valid results, see: T24291, T94231. */
BLI_assert(!is_negative_m3(mat));
- /* Check the trace of the matrix - bad precision if close to -1. */
- const float trace = mat[0][0] + mat[1][1] + mat[2][2];
-
- if (trace > 0) {
- float s = 2.0f * sqrtf(1.0f + trace);
-
- q[0] = 0.25f * s;
-
- s = 1.0f / s;
-
- q[1] = (mat[1][2] - mat[2][1]) * s;
- q[2] = (mat[2][0] - mat[0][2]) * s;
- q[3] = (mat[0][1] - mat[1][0]) * s;
- }
- else {
- /* Find the biggest diagonal element to choose the best formula.
- * Here trace should also be always >= 0, avoiding bad precision. */
- if (mat[0][0] > mat[1][1] && mat[0][0] > mat[2][2]) {
- float s = 2.0f * sqrtf(1.0f + mat[0][0] - mat[1][1] - mat[2][2]);
-
+ /* Method outlined by Mike Day, ref: https://math.stackexchange.com/a/3183435/220949
+ * with an additional `sqrtf(..)` for higher precision result.
+ * Removing the `sqrt` causes tests to fail unless the precision is set to 1e-6 or larger. */
+
+ if (mat[2][2] < 0.0f) {
+ if (mat[0][0] > mat[1][1]) {
+ const float trace = 1.0f + mat[0][0] - mat[1][1] - mat[2][2];
+ float s = 2.0f * sqrtf(trace);
+ if (mat[1][2] < mat[2][1]) {
+ /* Ensure W is non-negative for a canonical result. */
+ s = -s;
+ }
q[1] = 0.25f * s;
-
s = 1.0f / s;
-
q[0] = (mat[1][2] - mat[2][1]) * s;
- q[2] = (mat[1][0] + mat[0][1]) * s;
+ q[2] = (mat[0][1] + mat[1][0]) * s;
q[3] = (mat[2][0] + mat[0][2]) * s;
+ if (UNLIKELY((trace == 1.0f) && (q[0] == 0.0f && q[2] == 0.0f && q[3] == 0.0f))) {
+ /* Avoids the need to normalize the degenerate case. */
+ q[1] = 1.0f;
+ }
}
- else if (mat[1][1] > mat[2][2]) {
- float s = 2.0f * sqrtf(1.0f + mat[1][1] - mat[0][0] - mat[2][2]);
-
+ else {
+ const float trace = 1.0f - mat[0][0] + mat[1][1] - mat[2][2];
+ float s = 2.0f * sqrtf(trace);
+ if (mat[2][0] < mat[0][2]) {
+ /* Ensure W is non-negative for a canonical result. */
+ s = -s;
+ }
q[2] = 0.25f * s;
-
s = 1.0f / s;
-
q[0] = (mat[2][0] - mat[0][2]) * s;
- q[1] = (mat[1][0] + mat[0][1]) * s;
- q[3] = (mat[2][1] + mat[1][2]) * s;
+ q[1] = (mat[0][1] + mat[1][0]) * s;
+ q[3] = (mat[1][2] + mat[2][1]) * s;
+ if (UNLIKELY((trace == 1.0f) && (q[0] == 0.0f && q[1] == 0.0f && q[3] == 0.0f))) {
+ /* Avoids the need to normalize the degenerate case. */
+ q[2] = 1.0f;
+ }
}
- else {
- float s = 2.0f * sqrtf(1.0f + mat[2][2] - mat[0][0] - mat[1][1]);
-
+ }
+ else {
+ if (mat[0][0] < -mat[1][1]) {
+ const float trace = 1.0f - mat[0][0] - mat[1][1] + mat[2][2];
+ float s = 2.0f * sqrtf(trace);
+ if (mat[0][1] < mat[1][0]) {
+ /* Ensure W is non-negative for a canonical result. */
+ s = -s;
+ }
q[3] = 0.25f * s;
-
s = 1.0f / s;
-
q[0] = (mat[0][1] - mat[1][0]) * s;
q[1] = (mat[2][0] + mat[0][2]) * s;
- q[2] = (mat[2][1] + mat[1][2]) * s;
+ q[2] = (mat[1][2] + mat[2][1]) * s;
+ if (UNLIKELY((trace == 1.0f) && (q[0] == 0.0f && q[1] == 0.0f && q[2] == 0.0f))) {
+ /* Avoids the need to normalize the degenerate case. */
+ q[3] = 1.0f;
+ }
}
-
- /* Make sure W is non-negative for a canonical result. */
- if (q[0] < 0) {
- negate_v4(q);
+ else {
+ /* NOTE(@campbellbarton): A zero matrix will fall through to this block,
+ * needed so a zero scaled matrices to return a quaternion without rotation, see: T101848. */
+ const float trace = 1.0f + mat[0][0] + mat[1][1] + mat[2][2];
+ float s = 2.0f * sqrtf(trace);
+ q[0] = 0.25f * s;
+ s = 1.0f / s;
+ q[1] = (mat[1][2] - mat[2][1]) * s;
+ q[2] = (mat[2][0] - mat[0][2]) * s;
+ q[3] = (mat[0][1] - mat[1][0]) * s;
+ if (UNLIKELY((trace == 1.0f) && (q[1] == 0.0f && q[2] == 0.0f && q[3] == 0.0f))) {
+ /* Avoids the need to normalize the degenerate case. */
+ q[0] = 1.0f;
+ }
}
}
- normalize_qt(q);
+ BLI_assert(!(q[0] < 0.0f));
+ BLI_ASSERT_UNIT_QUAT(q);
}
static void mat3_normalized_to_quat_with_checks(float q[4], float mat[3][3])
diff --git a/source/blender/blenlib/intern/path_util.c b/source/blender/blenlib/intern/path_util.c
index d13f3fe5ced..2376bd82b69 100644
--- a/source/blender/blenlib/intern/path_util.c
+++ b/source/blender/blenlib/intern/path_util.c
@@ -123,7 +123,7 @@ int BLI_path_sequence_decode(const char *string, char *head, char *tail, ushort
void BLI_path_sequence_encode(
char *string, const char *head, const char *tail, ushort numlen, int pic)
{
- sprintf(string, "%s%.*d%s", head, numlen, MAX2(0, pic), tail);
+ BLI_sprintf(string, "%s%.*d%s", head, numlen, MAX2(0, pic), tail);
}
static int BLI_path_unc_prefix_len(const char *path); /* defined below in same file */
@@ -620,7 +620,7 @@ bool BLI_path_suffix(char *string, size_t maxlen, const char *suffix, const char
}
BLI_strncpy(extension, string + a, sizeof(extension));
- sprintf(string + a, "%s%s%s", sep, suffix, extension);
+ BLI_sprintf(string + a, "%s%s%s", sep, suffix, extension);
return true;
}
diff --git a/source/blender/blenlib/intern/string.c b/source/blender/blenlib/intern/string.c
index 755d2dbd55d..3c3dcaf90f4 100644
--- a/source/blender/blenlib/intern/string.c
+++ b/source/blender/blenlib/intern/string.c
@@ -241,6 +241,17 @@ char *BLI_sprintfN(const char *__restrict format, ...)
return n;
}
+int BLI_sprintf(char *__restrict str, const char *__restrict format, ...)
+{
+ va_list arg;
+
+ va_start(arg, format);
+ const int result = vsprintf(str, format, arg);
+ va_end(arg);
+
+ return result;
+}
+
/** \} */
/* -------------------------------------------------------------------- */
@@ -1114,7 +1125,7 @@ static size_t BLI_str_format_int_grouped_ex(char src[16], char dst[16], int num_
size_t BLI_str_format_int_grouped(char dst[16], int num)
{
char src[16];
- int num_len = sprintf(src, "%d", num);
+ const int num_len = BLI_snprintf(src, sizeof(src), "%d", num);
return BLI_str_format_int_grouped_ex(src, dst, num_len);
}
@@ -1124,7 +1135,7 @@ size_t BLI_str_format_uint64_grouped(char dst[16], uint64_t num)
/* NOTE: Buffer to hold maximum `uint64`, which is 1.8e+19. but
* we also need space for commas and null-terminator. */
char src[27];
- int num_len = sprintf(src, "%" PRIu64 "", num);
+ const int num_len = BLI_snprintf(src, sizeof(src), "%" PRIu64 "", num);
return BLI_str_format_int_grouped_ex(src, dst, num_len);
}
diff --git a/source/blender/blenlib/intern/task_scheduler.cc b/source/blender/blenlib/intern/task_scheduler.cc
index 1f7747453c1..5b056df78b4 100644
--- a/source/blender/blenlib/intern/task_scheduler.cc
+++ b/source/blender/blenlib/intern/task_scheduler.cc
@@ -8,6 +8,7 @@
#include "MEM_guardedalloc.h"
+#include "BLI_lazy_threading.hh"
#include "BLI_task.h"
#include "BLI_threads.h"
@@ -67,6 +68,7 @@ int BLI_task_scheduler_num_threads()
void BLI_task_isolate(void (*func)(void *userdata), void *userdata)
{
#ifdef WITH_TBB
+ blender::lazy_threading::ReceiverIsolation isolation;
tbb::this_task_arena::isolate([&] { func(userdata); });
#else
func(userdata);
diff --git a/source/blender/blenlib/intern/uuid.cc b/source/blender/blenlib/intern/uuid.cc
index 023dd1ec409..b845208f0da 100644
--- a/source/blender/blenlib/intern/uuid.cc
+++ b/source/blender/blenlib/intern/uuid.cc
@@ -5,6 +5,7 @@
*/
#include "BLI_assert.h"
+#include "BLI_string.h"
#include "BLI_uuid.h"
#include <cstdio>
@@ -85,19 +86,19 @@ bool BLI_uuid_equal(const bUUID uuid1, const bUUID uuid2)
void BLI_uuid_format(char *buffer, const bUUID uuid)
{
- std::sprintf(buffer,
- "%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x",
- uuid.time_low,
- uuid.time_mid,
- uuid.time_hi_and_version,
- uuid.clock_seq_hi_and_reserved,
- uuid.clock_seq_low,
- uuid.node[0],
- uuid.node[1],
- uuid.node[2],
- uuid.node[3],
- uuid.node[4],
- uuid.node[5]);
+ BLI_sprintf(buffer,
+ "%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x",
+ uuid.time_low,
+ uuid.time_mid,
+ uuid.time_hi_and_version,
+ uuid.clock_seq_hi_and_reserved,
+ uuid.clock_seq_low,
+ uuid.node[0],
+ uuid.node[1],
+ uuid.node[2],
+ uuid.node[3],
+ uuid.node[4],
+ uuid.node[5]);
}
bool BLI_uuid_parse_string(bUUID *uuid, const char *buffer)
diff --git a/source/blender/blenlib/intern/winstuff.c b/source/blender/blenlib/intern/winstuff.c
index 7e2c5e8f1dd..3a574b60ae2 100644
--- a/source/blender/blenlib/intern/winstuff.c
+++ b/source/blender/blenlib/intern/winstuff.c
@@ -110,7 +110,7 @@ bool BLI_windows_register_blend_extension(const bool background)
&hkey,
&dwd);
if (lresult == ERROR_SUCCESS) {
- sprintf(buffer, "\"%s\" \"%%1\"", BlPath);
+ BLI_snprintf(buffer, sizeof(buffer), "\"%s\" \"%%1\"", BlPath);
lresult = RegSetValueEx(hkey, NULL, 0, REG_SZ, (BYTE *)buffer, strlen(buffer) + 1);
RegCloseKey(hkey);
}
@@ -129,7 +129,7 @@ bool BLI_windows_register_blend_extension(const bool background)
&hkey,
&dwd);
if (lresult == ERROR_SUCCESS) {
- sprintf(buffer, "\"%s\", 1", BlPath);
+ BLI_snprintf(buffer, sizeof(buffer), "\"%s\", 1", BlPath);
lresult = RegSetValueEx(hkey, NULL, 0, REG_SZ, (BYTE *)buffer, strlen(buffer) + 1);
RegCloseKey(hkey);
}
@@ -167,10 +167,12 @@ bool BLI_windows_register_blend_extension(const bool background)
RegCloseKey(root);
printf("success (%s)\n", usr_mode ? "user" : "system");
if (!background) {
- sprintf(MBox,
- "File extension registered for %s.",
- usr_mode ? "the current user. To register for all users, run as an administrator" :
- "all users");
+ BLI_snprintf(MBox,
+ sizeof(MBox),
+ "File extension registered for %s.",
+ usr_mode ?
+ "the current user. To register for all users, run as an administrator" :
+ "all users");
MessageBox(0, MBox, "Blender", MB_OK | MB_ICONINFORMATION);
}
return true;
diff --git a/source/blender/blenlib/tests/BLI_math_rotation_test.cc b/source/blender/blenlib/tests/BLI_math_rotation_test.cc
index e37b212e1df..0c8ae38c386 100644
--- a/source/blender/blenlib/tests/BLI_math_rotation_test.cc
+++ b/source/blender/blenlib/tests/BLI_math_rotation_test.cc
@@ -3,6 +3,7 @@
#include "testing/testing.h"
#include "BLI_math_base.h"
+#include "BLI_math_matrix.h"
#include "BLI_math_rotation.h"
#include "BLI_math_rotation.hh"
#include "BLI_math_vector.hh"
@@ -138,6 +139,21 @@ TEST(math_rotation, quat_to_mat_to_quat_near_0001)
test_quat_to_mat_to_quat(0.30f, -0.030f, -0.30f, 0.95f);
}
+/* A zeroed matrix converted to a quaternion and back should not add rotation, see: T101848 */
+TEST(math_rotation, quat_to_mat_to_quat_zeroed_matrix)
+{
+ float matrix_zeroed[3][3] = {{0.0f}};
+ float matrix_result[3][3];
+ float matrix_unit[3][3];
+ float out_quat[4];
+
+ unit_m3(matrix_unit);
+ mat3_normalized_to_quat(out_quat, matrix_zeroed);
+ quat_to_mat3(matrix_result, out_quat);
+
+ EXPECT_M3_NEAR(matrix_unit, matrix_result, FLT_EPSILON);
+}
+
TEST(math_rotation, quat_split_swing_and_twist_negative)
{
const float input[4] = {-0.5f, 0, sqrtf(3) / 2, 0};