mirror of
https://github.com/blender/blender
synced 2026-09-27 01:34:15 +03:00
The main goal is to bring a bit of structure to the use of quaternions in the Cycles kernel. Prior to this change there was no dedicated quaternion type and float4 was used instead, and quaternions are stored as (x, y, z, w) matching naming between the quaternion and float4 fields. However, Blender uses (w, x, y, z) quaternion order for attributes, which is different from what Cycles uses. Without dedicated type this either leads to different quaternion orders depending whether it comes from attribute, or makes it intrinsically not possible to use implicit sharing. This change introduces Cycles Quaternion type which is compatible with Blender attribute math::Quaternion in both layout and alignment, making it possible to benefit from implicit sharing and use a nice structure in the kernel. This change does not modify the existing quaternion usage in the kernel which is currently used for transform decomposition and interpolation. There is currently no SIMD for the Quaternion type, which allows to avoid any special alignment requirement and share attributes with Blender, but performance might not be ideal. This attribute type will be used for representing gaussian splat rotation. The attribute access and interpolation matches behavior prior to this change. Added some basic tests for quaternion attribute access for RGB and alpha, mesh and volume attributes. Ref #159470 Pull Request: https://projects.blender.org/blender/blender/pulls/163342
319 lines
12 KiB
C++
319 lines
12 KiB
C++
/* SPDX-FileCopyrightText: 2011-2026 Blender Foundation
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0 */
|
|
|
|
/* Note: These fixtures test default micro-architecture optimization defined in the
|
|
* util/optimization.h. */
|
|
|
|
#include "util/math_float3x3.h"
|
|
|
|
#include <gmock/gmock.h>
|
|
#include <gtest/gtest.h>
|
|
|
|
#include "util/log.h"
|
|
#include "util/transform.h"
|
|
|
|
#include <iostream>
|
|
|
|
CCL_NAMESPACE_BEGIN
|
|
|
|
using testing::Not;
|
|
|
|
/* Use as:
|
|
* float3 vector1;
|
|
* float3 vector2;
|
|
* EXPECT_THAT(vector1, IsNearFloat3(vector2)); */
|
|
MATCHER_P2(IsNearFloat3, expected, eps, "")
|
|
{
|
|
for (int i = 0; i < 3; ++i) {
|
|
if (fabsf(arg[i] - expected[i]) > eps) {
|
|
*result_listener << "component " << i << " should be " << expected[i];
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/* Use as:
|
|
* float3x3 matrix1;
|
|
* float3x3 matrix2;
|
|
* EXPECT_THAT(matrix1, IsNearFloat3x3(matrix2)); */
|
|
MATCHER_P2(IsNearFloat3x3, expected, eps, "")
|
|
{
|
|
for (int row = 0; row < 3; ++row) {
|
|
for (int col = 0; col < 3; ++col) {
|
|
if (fabsf(arg[row][col] - expected[row][col]) > eps) {
|
|
*result_listener << "component (" << row << ", " << col << ") should be "
|
|
<< expected[row][col];
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/* TODO(sergey): Move to log.h? */
|
|
std::ostream &operator<<(std::ostream &os, const float3x3 &m)
|
|
{
|
|
os << "(" << m.x << ", " << m.y << ", " << m.z << ")";
|
|
return os;
|
|
}
|
|
|
|
class Float3x3Test : public ::testing::Test {
|
|
void SetUp() override
|
|
{
|
|
/* The micro-architecture check is not needed here, but use it here as a demonstration of how
|
|
* it can be implemented in a clear way. */
|
|
// GTEST_SKIP() << "Test skipped due to uarch capability";
|
|
}
|
|
};
|
|
|
|
/* Basic test for user-defined matcher. */
|
|
TEST_F(Float3x3Test, IsNearFloat3x3)
|
|
{
|
|
const float3x3 identity = identity_float3x3();
|
|
const float3x3 zero = zero_float3x3();
|
|
|
|
EXPECT_THAT(identity, IsNearFloat3x3(identity, 1e-6f));
|
|
EXPECT_THAT(zero, Not(IsNearFloat3x3(identity, 1e-6f)));
|
|
}
|
|
|
|
TEST_F(Float3x3Test, multiply_scalar_by_matrix)
|
|
{
|
|
/* >>> import numpy as np
|
|
* >>> a = np.array([[0.1, 0.2, 0.3], [1.1, 1.2, 1.3], [2.1, 2.2, 2.3]])
|
|
* >>> 3.2 * a
|
|
* array([[0.32, 0.64, 0.96],
|
|
* [3.52, 3.84, 4.16],
|
|
* [6.72, 7.04, 7.36]]) */
|
|
EXPECT_THAT(3.2f * make_float3x3(make_float3(0.1f, 0.2f, 0.3f),
|
|
make_float3(1.1f, 1.2f, 1.3f),
|
|
make_float3(2.1f, 2.2f, 2.3f)),
|
|
IsNearFloat3x3(make_float3x3(make_float3(0.32f, 0.64f, 0.96f),
|
|
make_float3(3.52f, 3.84f, 4.16f),
|
|
make_float3(6.72f, 7.04f, 7.36f)),
|
|
1e-6f));
|
|
}
|
|
|
|
TEST_F(Float3x3Test, multiply_matrix_by_scalar)
|
|
{
|
|
/* >>> import numpy as np
|
|
* >>> a = np.array([[0.1, 0.2, 0.3], [1.1, 1.2, 1.3], [2.1, 2.2, 2.3]])
|
|
* >>> a * 3.2
|
|
* array([[0.32, 0.64, 0.96],
|
|
* [3.52, 3.84, 4.16],
|
|
* [6.72, 7.04, 7.36]]) */
|
|
EXPECT_THAT(make_float3x3(make_float3(0.1f, 0.2f, 0.3f),
|
|
make_float3(1.1f, 1.2f, 1.3f),
|
|
make_float3(2.1f, 2.2f, 2.3f)) *
|
|
3.2f,
|
|
IsNearFloat3x3(make_float3x3(make_float3(0.32f, 0.64f, 0.96f),
|
|
make_float3(3.52f, 3.84f, 4.16f),
|
|
make_float3(6.72f, 7.04f, 7.36f)),
|
|
1e-6f));
|
|
}
|
|
|
|
TEST_F(Float3x3Test, multiply_matrix_by_matrix)
|
|
{
|
|
/* >>> import numpy as np
|
|
* >>> a = np.array([[0.1, 0.2, 0.3], [1.1, 1.2, 1.3], [2.1, 2.2, 2.3]])
|
|
* >>> b = np.array([[3.1, 3.2, 3.3], [4.1, 4.2, 4.3], [5.1, 5.2, 5.3]])
|
|
* >>> a @ b
|
|
* array([[ 2.66, 2.72, 2.78],
|
|
* [14.96, 15.32, 15.68],
|
|
* [27.26, 27.92, 28.58]]) */
|
|
EXPECT_THAT(make_float3x3(make_float3(0.1f, 0.2f, 0.3f),
|
|
make_float3(1.1f, 1.2f, 1.3f),
|
|
make_float3(2.1f, 2.2f, 2.3f)) *
|
|
make_float3x3(make_float3(3.1f, 3.2f, 3.3f),
|
|
make_float3(4.1f, 4.2f, 4.3f),
|
|
make_float3(5.1f, 5.2f, 5.3f)),
|
|
IsNearFloat3x3(make_float3x3(make_float3(2.66f, 2.72f, 2.78f),
|
|
make_float3(14.96f, 15.32f, 15.68f),
|
|
make_float3(27.26f, 27.92f, 28.58f)),
|
|
1e-5f));
|
|
}
|
|
|
|
TEST_F(Float3x3Test, multiply_matrix_by_vector)
|
|
{
|
|
/* >>> import numpy as np
|
|
* >>> a = np.array([[0.1, 0.2, 0.3], [1.1, 1.2, 1.3], [2.1, 2.2, 2.3]])
|
|
* >>> b = np.array([3.1, 3.2, 3.3])
|
|
* >>> a @ b
|
|
* array([ 1.94, 11.54, 21.14]) */
|
|
EXPECT_THAT(make_float3x3(make_float3(0.1f, 0.2f, 0.3f),
|
|
make_float3(1.1f, 1.2f, 1.3f),
|
|
make_float3(2.1f, 2.2f, 2.3f)) *
|
|
make_float3(3.1f, 3.2f, 3.3f),
|
|
IsNearFloat3(make_float3(1.94f, 11.54f, 21.14f), 1e-6f));
|
|
}
|
|
|
|
TEST_F(Float3x3Test, divide_matrix_by_scalar)
|
|
{
|
|
/* >>> import numpy as np
|
|
* >>> a = np.array([[0.1, 0.2, 0.3], [1.1, 1.2, 1.3], [2.1, 2.2, 2.3]])
|
|
* >>> a / 3.2
|
|
* array([[0.03125, 0.0625 , 0.09375],
|
|
* [0.34375, 0.375 , 0.40625],
|
|
* [0.65625, 0.6875 , 0.71875]]) */
|
|
EXPECT_THAT(make_float3x3(make_float3(0.1f, 0.2f, 0.3f),
|
|
make_float3(1.1f, 1.2f, 1.3f),
|
|
make_float3(2.1f, 2.2f, 2.3f)) /
|
|
3.2f,
|
|
IsNearFloat3x3(make_float3x3(make_float3(0.03125f, 0.0625f, 0.09375f),
|
|
make_float3(0.34375f, 0.375f, 0.40625f),
|
|
make_float3(0.65625f, 0.6875f, 0.71875f)),
|
|
1e-6f));
|
|
}
|
|
|
|
TEST_F(Float3x3Test, float3x3_scale)
|
|
{
|
|
EXPECT_THAT(float3x3_scale(make_float3(0.1f, 1.2f, 2.3f)),
|
|
IsNearFloat3x3(make_float3x3(make_float3(0.1f, 0.0f, 0.0f),
|
|
make_float3(0.0f, 1.2f, 0.0f),
|
|
make_float3(0.0f, 0.0f, 2.3f)),
|
|
1e-6f));
|
|
}
|
|
|
|
TEST_F(Float3x3Test, transposed)
|
|
{
|
|
EXPECT_THAT(transposed(make_float3x3(make_float3(0.0f, 0.1f, 0.2f),
|
|
make_float3(1.0f, 1.1f, 1.2f),
|
|
make_float3(2.0f, 2.1f, 2.2f))),
|
|
IsNearFloat3x3(make_float3x3(make_float3(0.0f, 1.0f, 2.0f),
|
|
make_float3(0.1f, 1.1f, 2.1f),
|
|
make_float3(0.2f, 1.2f, 2.2f)),
|
|
1e-6f));
|
|
}
|
|
|
|
TEST_F(Float3x3Test, determinant)
|
|
{
|
|
/* >>> import numpy as np
|
|
* >>> a = np.array([[0.1, 0.2, 0.3], [1.3, 1.2, 1.1], [2.2, 2.1, 2.3]])
|
|
* >>> np.linalg.det(a)
|
|
* np.float64(-0.04199999999999992) */
|
|
EXPECT_NEAR(determinant(make_float3x3(make_float3(0.1f, 0.2f, 0.3f),
|
|
make_float3(1.3f, 1.2f, 1.1f),
|
|
make_float3(2.2f, 2.1f, 2.3f))),
|
|
-0.042f,
|
|
1e-6f);
|
|
}
|
|
|
|
TEST_F(Float3x3Test, adjoint)
|
|
{
|
|
EXPECT_THAT(adjoint(make_float3x3(make_float3(-3.0f, 2.0f, -5.0f),
|
|
make_float3(-1.0f, 0.0f, -2.0f),
|
|
make_float3(3.0f, -4.0f, 1.0f))),
|
|
IsNearFloat3x3(make_float3x3(make_float3(-8.0f, 18.0f, -4.0f),
|
|
make_float3(-5.0f, 12.0f, -1.0f),
|
|
make_float3(4.0f, -6.0f, 2.0f)),
|
|
1e-6f));
|
|
}
|
|
|
|
TEST_F(Float3x3Test, inverted)
|
|
{
|
|
/* >>> import numpy as np
|
|
* >>> a = np.array([[0.1, 0.2, 0.3], [1.3, 1.2, 1.1], [2.2, 2.1, 2.3]])
|
|
* >>> np.linalg.inv(a)
|
|
* array([[-10.71428571, -4.04761905, 3.33333333],
|
|
[ 13.57142857, 10.23809524, -6.66666667],
|
|
[ -2.14285714, -5.47619048, 3.33333333]]) */
|
|
const float3x3 matrix = make_float3x3(
|
|
make_float3(0.1f, 0.2f, 0.3f), make_float3(1.3f, 1.2f, 1.1f), make_float3(2.2f, 2.1f, 2.3f));
|
|
EXPECT_THAT(inverted(matrix),
|
|
IsNearFloat3x3(make_float3x3(make_float3(-10.71428571f, -4.04761905f, 3.33333333f),
|
|
make_float3(13.57142857f, 10.23809524f, -6.66666667f),
|
|
make_float3(-2.14285714f, -5.47619048f, 3.33333333f)),
|
|
1e-4f));
|
|
EXPECT_THAT(matrix * inverted(matrix), IsNearFloat3x3(identity_float3x3(), 1e-5f));
|
|
EXPECT_THAT(inverted(matrix) * matrix, IsNearFloat3x3(identity_float3x3(), 1e-5f));
|
|
}
|
|
|
|
TEST_F(Float3x3Test, quaternion_to_scaled_rotation)
|
|
{
|
|
EXPECT_THAT(quaternion_to_scaled_rotation(
|
|
make_quaternion(2.0f * cosf(M_PI_F / 4.0f), 0, 0, 2.0f * sinf(M_PI_F / 4.0f))),
|
|
IsNearFloat3x3(make_float3x3(make_float3(0.0f, -4.0f, 0.0f),
|
|
make_float3(4.0f, 0.0f, 0.0f),
|
|
make_float3(0.0f, 0.0f, 4.0f)),
|
|
1e-6f));
|
|
}
|
|
|
|
TEST_F(Float3x3Test, quaternion_to_rotation)
|
|
{
|
|
/* >>> from scipy.spatial.transform import Rotation as R
|
|
* >>> import numpy as np
|
|
* >>> r = R.from_quat([np.cos(np.pi/4), 0, 0, np.sin(np.pi/4)], scalar_first=True)
|
|
* >>> r.as_matrix()
|
|
* array([[ 2.22044605e-16, -1.00000000e+00, 0.00000000e+00],
|
|
* [ 1.00000000e+00, 2.22044605e-16, 0.00000000e+00],
|
|
* [ 0.00000000e+00, 0.00000000e+00, 1.00000000e+00]]) */
|
|
EXPECT_THAT(
|
|
quaternion_to_rotation(make_quaternion(cosf(M_PI_F / 4.0f), 0, 0, sinf(M_PI_F / 4.0f))),
|
|
IsNearFloat3x3(make_float3x3(make_float3(0.0f, -1.0f, 0.0f),
|
|
make_float3(1.0f, 0.0f, 0.0f),
|
|
make_float3(0.0f, 0.0f, 1.0f)),
|
|
1e-6f));
|
|
EXPECT_THAT(quaternion_to_rotation(
|
|
make_quaternion(2.0f * cosf(M_PI_F / 4.0f), 0, 0, 2.0f * sinf(M_PI_F / 4.0f))),
|
|
IsNearFloat3x3(make_float3x3(make_float3(0.0f, -1.0f, 0.0f),
|
|
make_float3(1.0f, 0.0f, 0.0f),
|
|
make_float3(0.0f, 0.0f, 1.0f)),
|
|
1e-6f));
|
|
|
|
{
|
|
Transform tfm = transform_euler(make_float3(0.1f, -0.2f, 0.3f));
|
|
const float4 q = transform_to_quat(tfm);
|
|
const float3x3 R = quaternion_to_rotation(make_quaternion(q.w, q.x, q.y, q.z));
|
|
EXPECT_THAT(R.x, IsNearFloat3(tfm.x, 1e-6f));
|
|
EXPECT_THAT(R.y, IsNearFloat3(tfm.y, 1e-6f));
|
|
EXPECT_THAT(R.z, IsNearFloat3(tfm.z, 1e-6f));
|
|
}
|
|
}
|
|
|
|
class PackedFloat3x3Test : public Float3x3Test {};
|
|
|
|
TEST_F(PackedFloat3x3Test, init_from_float3x3)
|
|
{
|
|
const packed_float3x3 packed(make_float3x3(make_float3(0.0f, 0.1f, 0.2f),
|
|
make_float3(1.0f, 1.1f, 1.2f),
|
|
make_float3(2.0f, 2.1f, 2.2f)));
|
|
EXPECT_NEAR(packed.x.x, 0.0f, 1e-6f);
|
|
EXPECT_NEAR(packed.x.y, 0.1f, 1e-6f);
|
|
EXPECT_NEAR(packed.x.z, 0.2f, 1e-6f);
|
|
|
|
EXPECT_NEAR(packed.y.x, 1.0f, 1e-6f);
|
|
EXPECT_NEAR(packed.y.y, 1.1f, 1e-6f);
|
|
EXPECT_NEAR(packed.y.z, 1.2f, 1e-6f);
|
|
|
|
EXPECT_NEAR(packed.z.x, 2.0f, 1e-6f);
|
|
EXPECT_NEAR(packed.z.y, 2.1f, 1e-6f);
|
|
EXPECT_NEAR(packed.z.z, 2.2f, 1e-6f);
|
|
}
|
|
|
|
TEST_F(PackedFloat3x3Test, cast_to_float3x3)
|
|
{
|
|
const packed_float3x3 packed(make_float3x3(make_float3(0.0f, 0.1f, 0.2f),
|
|
make_float3(1.0f, 1.1f, 1.2f),
|
|
make_float3(2.0f, 2.1f, 2.2f)));
|
|
const float3x3 unpacked = packed;
|
|
EXPECT_THAT(unpacked,
|
|
IsNearFloat3x3(make_float3x3(make_float3(0.0f, 0.1f, 0.2f),
|
|
make_float3(1.0f, 1.1f, 1.2f),
|
|
make_float3(2.0f, 2.1f, 2.2f)),
|
|
1e-6f));
|
|
}
|
|
|
|
TEST_F(PackedFloat3x3Test, assign_from_float3x3)
|
|
{
|
|
packed_float3x3 packed = identity_float3x3();
|
|
packed = make_float3x3(
|
|
make_float3(0.0f, 0.1f, 0.2f), make_float3(1.0f, 1.1f, 1.2f), make_float3(2.0f, 2.1f, 2.2f));
|
|
EXPECT_THAT(float3x3(packed),
|
|
IsNearFloat3x3(make_float3x3(make_float3(0.0f, 0.1f, 0.2f),
|
|
make_float3(1.0f, 1.1f, 1.2f),
|
|
make_float3(2.0f, 2.1f, 2.2f)),
|
|
1e-6f));
|
|
}
|
|
|
|
CCL_NAMESPACE_END
|