1+ using System . Collections . Generic ;
2+ using NUnit . Framework ;
3+ using Burst . Compiler . IL . Tests ;
4+
5+ namespace Unity . Mathematics . Tests
6+ {
7+ [ TestFixture ]
8+ public class TestSvd
9+ {
10+ const float k_SVDTolerance = 1e-4f ;
11+
12+ static bool QuaternionEquals ( quaternion expected , quaternion actual , float tolerance ) =>
13+ ( math . lengthsq ( expected ) == 0f && math . lengthsq ( actual ) == 0f ) || math . abs ( math . dot ( expected , actual ) ) > ( 1f - tolerance ) ;
14+
15+ // Validate Penrose condition that [aba = a]
16+ static void ValidatePenrose1 ( in float3x3 a , in float3x3 b )
17+ {
18+ var testA = math . mul ( math . mul ( a , b ) , a ) ;
19+
20+ TestUtils . AreEqual ( a . c0 . x , testA . c0 . x , k_SVDTolerance ) ;
21+ TestUtils . AreEqual ( a . c0 . y , testA . c0 . y , k_SVDTolerance ) ;
22+ TestUtils . AreEqual ( a . c0 . z , testA . c0 . z , k_SVDTolerance ) ;
23+ TestUtils . AreEqual ( a . c1 . x , testA . c1 . x , k_SVDTolerance ) ;
24+ TestUtils . AreEqual ( a . c1 . y , testA . c1 . y , k_SVDTolerance ) ;
25+ TestUtils . AreEqual ( a . c1 . z , testA . c1 . z , k_SVDTolerance ) ;
26+ TestUtils . AreEqual ( a . c2 . x , testA . c2 . x , k_SVDTolerance ) ;
27+ TestUtils . AreEqual ( a . c2 . y , testA . c2 . y , k_SVDTolerance ) ;
28+ TestUtils . AreEqual ( a . c2 . z , testA . c2 . z , k_SVDTolerance ) ;
29+ }
30+
31+ // Validate Penrose condition that [transpose(ab) = ab]
32+ static void ValidatePenrose2 ( in float3x3 a , in float3x3 b )
33+ {
34+ var ab = math . mul ( a , b ) ;
35+ var testAB = math . transpose ( ab ) ;
36+
37+ TestUtils . AreEqual ( ab . c0 . x , testAB . c0 . x , k_SVDTolerance ) ;
38+ TestUtils . AreEqual ( ab . c0 . y , testAB . c0 . y , k_SVDTolerance ) ;
39+ TestUtils . AreEqual ( ab . c0 . z , testAB . c0 . z , k_SVDTolerance ) ;
40+ TestUtils . AreEqual ( ab . c1 . x , testAB . c1 . x , k_SVDTolerance ) ;
41+ TestUtils . AreEqual ( ab . c1 . y , testAB . c1 . y , k_SVDTolerance ) ;
42+ TestUtils . AreEqual ( ab . c1 . z , testAB . c1 . z , k_SVDTolerance ) ;
43+ TestUtils . AreEqual ( ab . c2 . x , testAB . c2 . x , k_SVDTolerance ) ;
44+ TestUtils . AreEqual ( ab . c2 . y , testAB . c2 . y , k_SVDTolerance ) ;
45+ TestUtils . AreEqual ( ab . c2 . z , testAB . c2 . z , k_SVDTolerance ) ;
46+ }
47+
48+ static void ValidateSingular ( in float3x3 a ) =>
49+ TestUtils . AreEqual ( 0.0f , math . determinant ( a ) , k_SVDTolerance ) ;
50+
51+ [ TestCompiler ]
52+ public static void CanSVDInverseNonSingularFloat3x3 ( )
53+ {
54+ var mat = math . float3x3 (
55+ math . float3 ( 9f , 1f , 2f ) ,
56+ math . float3 ( 3f , 8f , 4f ) ,
57+ math . float3 ( 5f , 6f , 7f )
58+ ) ;
59+
60+ var inv = svd . svdInverse ( mat ) ;
61+ var testIdentity = math . mul ( mat , inv ) ;
62+
63+ TestUtils . AreEqual ( 1f , testIdentity . c0 . x , k_SVDTolerance ) ;
64+ TestUtils . AreEqual ( 0f , testIdentity . c0 . y , k_SVDTolerance ) ;
65+ TestUtils . AreEqual ( 0f , testIdentity . c0 . z , k_SVDTolerance ) ;
66+ TestUtils . AreEqual ( 0f , testIdentity . c1 . x , k_SVDTolerance ) ;
67+ TestUtils . AreEqual ( 1f , testIdentity . c1 . y , k_SVDTolerance ) ;
68+ TestUtils . AreEqual ( 0f , testIdentity . c1 . z , k_SVDTolerance ) ;
69+ TestUtils . AreEqual ( 0f , testIdentity . c2 . x , k_SVDTolerance ) ;
70+ TestUtils . AreEqual ( 0f , testIdentity . c2 . y , k_SVDTolerance ) ;
71+ TestUtils . AreEqual ( 1f , testIdentity . c2 . z , k_SVDTolerance ) ;
72+ }
73+
74+ [ TestCompiler ]
75+ public static void CanSVDInverseFloat3x3With_NullColumn ( )
76+ {
77+ var mat = math . float3x3 (
78+ math . float3 ( 9f , 1f , 0f ) ,
79+ math . float3 ( 3f , 8f , 0f ) ,
80+ math . float3 ( 5f , 6f , 0f )
81+ ) ;
82+
83+ ValidateSingular ( mat ) ;
84+ var inv = svd . svdInverse ( mat ) ;
85+
86+ ValidatePenrose1 ( mat , inv ) ;
87+ ValidatePenrose1 ( inv , mat ) ;
88+ ValidatePenrose2 ( mat , inv ) ;
89+ ValidatePenrose2 ( inv , mat ) ;
90+ }
91+
92+ [ TestCompiler ]
93+ public static void CanSVDInverseFloat3x3With_NullRow ( )
94+ {
95+ var mat = math . float3x3 (
96+ math . float3 ( 9f , 1f , 2f ) ,
97+ math . float3 ( 0f , 0f , 0f ) ,
98+ math . float3 ( 5f , 6f , 7f )
99+ ) ;
100+
101+ ValidateSingular ( mat ) ;
102+ var inv = svd . svdInverse ( mat ) ;
103+
104+ ValidatePenrose1 ( mat , inv ) ;
105+ ValidatePenrose1 ( inv , mat ) ;
106+ ValidatePenrose2 ( mat , inv ) ;
107+ ValidatePenrose2 ( inv , mat ) ;
108+ }
109+
110+ [ TestCompiler ]
111+ public static void CanSVDInverseFloat3x3With_LinearDependentColumn ( )
112+ {
113+ var mat = math . float3x3 (
114+ math . float3 ( 9f , 4f , 2f ) ,
115+ math . float3 ( 3f , 8f , 4f ) ,
116+ math . float3 ( 5f , 14f , 7f )
117+ ) ;
118+
119+ ValidateSingular ( mat ) ;
120+ var inv = svd . svdInverse ( mat ) ;
121+
122+ ValidatePenrose1 ( mat , inv ) ;
123+ ValidatePenrose1 ( inv , mat ) ;
124+ ValidatePenrose2 ( mat , inv ) ;
125+ ValidatePenrose2 ( inv , mat ) ;
126+ }
127+
128+ [ TestCompiler ]
129+ public static void CanSVDInverseFloat3x3With_LinearDependentRow ( )
130+ {
131+ var mat = math . float3x3 (
132+ math . float3 ( 9f , 1f , 2f ) ,
133+ math . float3 ( 10f , 12f , 14f ) ,
134+ math . float3 ( 5f , 6f , 7f )
135+ ) ;
136+
137+ ValidateSingular ( mat ) ;
138+ var inv = svd . svdInverse ( mat ) ;
139+
140+ ValidatePenrose1 ( mat , inv ) ;
141+ ValidatePenrose1 ( inv , mat ) ;
142+ ValidatePenrose2 ( mat , inv ) ;
143+ ValidatePenrose2 ( inv , mat ) ;
144+ }
145+
146+ [ TestCompiler ]
147+ public static void CanSVDInverseFloat3x3With_RotatedZeroScale ( )
148+ {
149+ var m102030 = math . float3x3 ( quaternion . Euler ( math . radians ( 10f ) , math . radians ( 20f ) , math . radians ( 30f ) ) ) ;
150+ var parent = math . mulScale ( m102030 , math . float3 ( 1f , 1f , 0f ) ) ;
151+ var mat = math . mul ( parent , m102030 ) ;
152+
153+ ValidateSingular ( mat ) ;
154+ var inv = svd . svdInverse ( mat ) ;
155+
156+ ValidatePenrose1 ( mat , inv ) ;
157+ ValidatePenrose1 ( inv , mat ) ;
158+ ValidatePenrose2 ( mat , inv ) ;
159+ ValidatePenrose2 ( inv , mat ) ;
160+ }
161+
162+ // Case 928598: The errors appear, when GameObject has a child with ParticleSystem which is rotated along the y-axis to -180 and is moved
163+ [ TestCompiler ]
164+ public static void CanExtractSVDRotationFromFloat3x3With_X180_Y0_Z181 ( )
165+ {
166+ var q = quaternion . Euler ( math . radians ( 180f ) , math . radians ( 0f ) , math . radians ( 181f ) ) ;
167+ var qSVD = svd . svdRotation ( math . float3x3 ( q ) ) ;
168+
169+ TestUtils . IsTrue ( QuaternionEquals ( q , qSVD , k_SVDTolerance ) ) ;
170+ }
171+
172+ // Case 938548: Assertion failed on expression: 'CompareApproximately(det, 1.0F, .005f)' when scaling system to 0 on at least 2 axes
173+ [ TestCompiler ]
174+ public static void CanExtractSVDRotationFromFloat3x3With_ZeroScaleXY ( )
175+ {
176+ var q0 = quaternion . Euler ( math . radians ( 10f ) , math . radians ( 20f ) , math . radians ( 30f ) ) ;
177+ var m0 = math . float3x3 ( q0 ) ;
178+ var m0Scaled = math . mulScale ( m0 , math . float3 ( 1f , 0f , 0f ) ) ;
179+ var q1 = svd . svdRotation ( m0Scaled ) ;
180+ var m1 = math . float3x3 ( q1 ) ;
181+
182+ TestUtils . AreEqual ( 0.0f , math . length ( m0 . c0 - m1 . c0 ) , k_SVDTolerance ) ;
183+ }
184+ }
185+ }
0 commit comments