diff --git a/packages/react-native/ReactCommon/react/renderer/graphics/Transform.cpp b/packages/react-native/ReactCommon/react/renderer/graphics/Transform.cpp index 3cd227e0292df3..a7191dc2828cbe 100644 --- a/packages/react-native/ReactCommon/react/renderer/graphics/Transform.cpp +++ b/packages/react-native/ReactCommon/react/renderer/graphics/Transform.cpp @@ -256,6 +256,70 @@ bool Transform::isHorizontalInversion(const Transform& transform) { return transform.at(0, 0) == -1; } +Transform Transform::Inverse(const Transform& transform) { + auto m00 = transform.matrix[0]; + auto m01 = transform.matrix[1]; + auto m02 = transform.matrix[2]; + auto m03 = transform.matrix[3]; + auto m10 = transform.matrix[4]; + auto m11 = transform.matrix[5]; + auto m12 = transform.matrix[6]; + auto m13 = transform.matrix[7]; + auto m20 = transform.matrix[8]; + auto m21 = transform.matrix[9]; + auto m22 = transform.matrix[10]; + auto m23 = transform.matrix[11]; + auto m30 = transform.matrix[12]; + auto m31 = transform.matrix[13]; + auto m32 = transform.matrix[14]; + auto m33 = transform.matrix[15]; + + auto A2323 = m22 * m33 - m23 * m32; + auto A1323 = m21 * m33 - m23 * m31; + auto A1223 = m21 * m32 - m22 * m31; + auto A0323 = m20 * m33 - m23 * m30; + auto A0223 = m20 * m32 - m22 * m30; + auto A0123 = m20 * m31 - m21 * m30; + auto A2313 = m12 * m33 - m13 * m32; + auto A1313 = m11 * m33 - m13 * m31; + auto A1213 = m11 * m32 - m12 * m31; + auto A2312 = m12 * m23 - m13 * m22; + auto A1312 = m11 * m23 - m13 * m21; + auto A1212 = m11 * m22 - m12 * m21; + auto A0313 = m10 * m33 - m13 * m30; + auto A0213 = m10 * m32 - m12 * m30; + auto A0312 = m10 * m23 - m13 * m20; + auto A0212 = m10 * m22 - m12 * m20; + auto A0113 = m10 * m31 - m11 * m30; + auto A0112 = m10 * m21 - m11 * m20; + + auto det = m00 * (m11 * A2323 - m12 * A1323 + m13 * A1223) - + m01 * (m10 * A2323 - m12 * A0323 + m13 * A0223) + + m02 * (m10 * A1323 - m11 * A0323 + m13 * A0123) - + m03 * (m10 * A1223 - m11 * A0223 + m12 * A0123); + det = 1 / det; + + auto result = Transform{}; + result.matrix[0] = det * (m11 * A2323 - m12 * A1323 + m13 * A1223); + result.matrix[1] = det * -(m01 * A2323 - m02 * A1323 + m03 * A1223); + result.matrix[2] = det * (m01 * A2313 - m02 * A1313 + m03 * A1213); + result.matrix[3] = det * -(m01 * A2312 - m02 * A1312 + m03 * A1212); + result.matrix[4] = det * -(m10 * A2323 - m12 * A0323 + m13 * A0223); + result.matrix[5] = det * (m00 * A2323 - m02 * A0323 + m03 * A0223); + result.matrix[6] = det * -(m00 * A2313 - m02 * A0313 + m03 * A0213); + result.matrix[7] = det * (m00 * A2312 - m02 * A0312 + m03 * A0212); + result.matrix[8] = det * (m10 * A1323 - m11 * A0323 + m13 * A0123); + result.matrix[9] = det * -(m00 * A1323 - m01 * A0323 + m03 * A0123); + result.matrix[10] = det * (m00 * A1313 - m01 * A0313 + m03 * A0113); + result.matrix[11] = det * -(m00 * A1312 - m01 * A0312 + m03 * A0112); + result.matrix[12] = det * -(m10 * A1223 - m11 * A0223 + m12 * A0123); + result.matrix[13] = det * (m00 * A1223 - m01 * A0223 + m02 * A0123); + result.matrix[14] = det * -(m00 * A1213 - m01 * A0213 + m02 * A0113); + result.matrix[15] = det * (m00 * A1212 - m01 * A0212 + m02 * A0112); + + return result; +} + bool Transform::operator==(const Transform& rhs) const { for (auto i = 0; i < 16; i++) { if (matrix[i] != rhs.matrix[i]) { diff --git a/packages/react-native/ReactCommon/react/renderer/graphics/Transform.h b/packages/react-native/ReactCommon/react/renderer/graphics/Transform.h index 1ed8353862b8ee..4c25018d6dfe6b 100644 --- a/packages/react-native/ReactCommon/react/renderer/graphics/Transform.h +++ b/packages/react-native/ReactCommon/react/renderer/graphics/Transform.h @@ -156,6 +156,8 @@ struct Transform { static bool isVerticalInversion(const Transform& transform); static bool isHorizontalInversion(const Transform& transform); + static Transform Inverse(const Transform& transform); + /* * Equality operators. */ diff --git a/packages/react-native/ReactCommon/react/renderer/graphics/tests/TransformTest.cpp b/packages/react-native/ReactCommon/react/renderer/graphics/tests/TransformTest.cpp index dcbb3052e68b10..e6bf30aff2d42d 100644 --- a/packages/react-native/ReactCommon/react/renderer/graphics/tests/TransformTest.cpp +++ b/packages/react-native/ReactCommon/react/renderer/graphics/tests/TransformTest.cpp @@ -9,9 +9,19 @@ #include #include +#include using namespace facebook::react; +static inline void checkMatrix( + const Transform& m, + const Transform& exp, + float epsilon = std::numeric_limits::epsilon()) { + for (int i = 0; i < 16; i++) { + EXPECT_NEAR(m.matrix[i], exp.matrix[i], epsilon); + } +} + TEST(TransformTest, transformingSize) { auto size = facebook::react::Size{100, 200}; auto scaledSize = size * Transform::Scale(0.5, 0.5, 1); @@ -90,3 +100,51 @@ TEST(TransformTest, scalingAndTranslatingRect) { EXPECT_EQ(transformedRect.size.width, 150); EXPECT_EQ(transformedRect.size.height, 200); } + +// Inverse Transform Tests (adapted from WPT: +// css/geometry/DOMMatrix-invert-invertible) +TEST(TransformTest, inverseIdentity) { + auto m = Transform::Identity(); + auto m1 = Transform::Inverse(m); + checkMatrix(m, m1); +} + +TEST(TransformTest, inverseTranslate) { + auto m = Transform::Translate(10, -20.5, 0); + auto m1 = Transform::Inverse(m); + checkMatrix(m1, Transform::Translate(-10, 20.5, 0)); + checkMatrix(m * m1, Transform::Identity()); +} + +TEST(TransformTest, inverse3DTranslate) { + auto m = Transform::Translate(10, -20.5, 30.5); + auto m1 = Transform::Inverse(m); + checkMatrix(m1, Transform::Translate(-10, 20.5, -30.5)); + checkMatrix(m * m1, Transform::Identity()); +} + +TEST(TransformTest, inverseScale) { + auto m = Transform::Scale(4, -0.5, 1); + auto m1 = Transform::Inverse(m); + checkMatrix(m1, Transform::Scale(0.25, -2.0, 1)); + checkMatrix(m * m1, Transform::Identity()); +} + +TEST(TransformTest, inverse3DScale) { + auto m = Transform::Scale(4, -0.5, 2); + auto m1 = Transform::Inverse(m); + checkMatrix(m1, Transform::Scale(0.25, -2.0, 0.5)); + checkMatrix(m * m1, Transform::Identity()); +} + +TEST(TransformTest, inverseComplex) { + auto m = Transform::RotateX(20) * Transform::Translate(10, -20.5, 30.5) * + Transform::RotateY(10) * Transform::Scale(10, -0.5, 2.5) * + Transform::RotateZ(-30); + auto expected = Transform::RotateZ(30) * Transform::Scale(0.1, -2.0, 0.4) * + Transform::RotateY(-10) * Transform::Translate(-10, 20.5, -30.5) * + Transform::RotateX(-20); + auto m1 = Transform::Inverse(m); + auto epsilon = 1e-6f; + checkMatrix(m1, expected, epsilon); +}