1 /*
2  * Copyright 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package androidx.xr.runtime.math
18 
19 import com.google.common.truth.Truth.assertThat
20 import kotlin.math.sqrt
21 import org.junit.Test
22 import org.junit.runner.RunWith
23 import org.junit.runners.JUnit4
24 
25 @RunWith(JUnit4::class)
26 class PoseTest {
27 
28     @Test
constructor_noArguments_returnsZeroVectorAndIdentityQuaternionnull29     fun constructor_noArguments_returnsZeroVectorAndIdentityQuaternion() {
30         val underTest = Pose()
31 
32         assertThat(underTest.translation).isEqualTo(Vector3(0f, 0f, 0f))
33         assertThat(underTest.rotation).isEqualTo(Quaternion(0f, 0f, 0f, 1f))
34     }
35 
36     @Test
equals_sameValues_returnsTruenull37     fun equals_sameValues_returnsTrue() {
38         val underTest =
39             Pose(translation = Vector3(1f, 2f, 3f), rotation = Quaternion(4f, 5f, 6f, 7f))
40         val underTest2 =
41             Pose(translation = Vector3(1f, 2f, 3f), rotation = Quaternion(4f, 5f, 6f, 7f))
42 
43         assertThat(underTest).isEqualTo(underTest2)
44     }
45 
46     @Test
equals_differentValues_returnsFalsenull47     fun equals_differentValues_returnsFalse() {
48         val underTest =
49             Pose(translation = Vector3(1f, 2f, 3f), rotation = Quaternion(4f, 5f, 6f, 7f))
50         val underTest2 =
51             Pose(translation = Vector3(9f, 10f, 11f), rotation = Quaternion(4f, 5f, 6f, 7f))
52         val underTest3 = Vector3()
53 
54         assertThat(underTest).isNotEqualTo(underTest2)
55         assertThat(underTest).isNotEqualTo(underTest3)
56     }
57 
58     @Test
hashCodeEquals_sameValues_returnsTruenull59     fun hashCodeEquals_sameValues_returnsTrue() {
60         val underTest =
61             Pose(translation = Vector3(1f, 2f, 3f), rotation = Quaternion(4f, 5f, 6f, 7f))
62         val underTest2 =
63             Pose(translation = Vector3(1f, 2f, 3f), rotation = Quaternion(4f, 5f, 6f, 7f))
64 
65         assertThat(underTest.hashCode()).isEqualTo(underTest2.hashCode())
66     }
67 
68     @Test
hashCodeEquals_differentValues_returnsFalsenull69     fun hashCodeEquals_differentValues_returnsFalse() {
70         val underTest =
71             Pose(translation = Vector3(1f, 2f, 3f), rotation = Quaternion(4f, 5f, 6f, 7f))
72         val underTest2 =
73             Pose(translation = Vector3(9f, 10f, 11f), rotation = Quaternion(4f, 5f, 6f, 7f))
74         val underTest3 = Vector3()
75 
76         assertThat(underTest.hashCode()).isNotEqualTo(underTest2.hashCode())
77         assertThat(underTest.hashCode()).isNotEqualTo(underTest3.hashCode())
78     }
79 
80     @Test
constructorEquals_expectedToString_returnsTruenull81     fun constructorEquals_expectedToString_returnsTrue() {
82         val underTest =
83             Pose(translation = Vector3(1f, 2f, 3f), rotation = Quaternion(4f, 5f, 6f, 7f))
84         val underTest2 = Pose()
85 
86         assertThat(underTest.toString())
87             .isEqualTo(
88                 "Pose{\n\tTranslation=[x=1.0, y=2.0, z=3.0]\n\tRotation=[x=0.35634834, y=0.4454354, z=0.5345225, w=0.6236096]\n}"
89             )
90         assertThat(underTest2.toString())
91             .isEqualTo(
92                 "Pose{\n\tTranslation=[x=0.0, y=0.0, z=0.0]\n\tRotation=[x=0.0, y=0.0, z=0.0, w=1.0]\n}"
93             )
94     }
95 
96     @Test
distance_returnsLengthOfVectorBetweenTranslationsnull97     fun distance_returnsLengthOfVectorBetweenTranslations() {
98         val underTest = Pose(translation = Vector3(0F, 3f, 4F), rotation = Quaternion())
99 
100         // (0, 3, 4) - (0, 0, 0) = (0, 3, 4) -> sqrt(0^2 + 3^2 + 4^2) = sqrt(25)
101         assertThat(Pose.distance(underTest, Pose())).isEqualTo(5F)
102     }
103 
104     @Test
constructor_fromPose_returnsSameValuesnull105     fun constructor_fromPose_returnsSameValues() {
106         val underTest =
107             Pose(translation = Vector3(1f, 2f, 3f), rotation = Quaternion(4f, 5f, 6f, 7f))
108         val underTest2 = Pose(underTest)
109 
110         assertThat(underTest).isEqualTo(underTest2)
111     }
112 
113     @Test
compose_returnsPoseWithTranslationAndRotationnull114     fun compose_returnsPoseWithTranslationAndRotation() {
115         val underTest =
116             Pose(
117                 translation = Vector3(3f, 0f, 0f),
118                 rotation = Quaternion(0f, sqrt(2f) / 2, 0f, sqrt(2f) / 2),
119             )
120         val underTest2 =
121             Pose(
122                 translation = Vector3(0f, 0f, -3f),
123                 rotation = Quaternion(0f, sqrt(2f) / 2, 0f, sqrt(2f) / 2),
124             )
125 
126         val underTestCompose = underTest.compose(underTest2)
127 
128         assertTranslation(underTestCompose.translation, 0f, 0f, 0f)
129         assertRotation(underTestCompose.rotation, 0f, 1f, 0f, 0f)
130     }
131 
132     @Test
translate_withZeroVector3_returnsSamePosenull133     fun translate_withZeroVector3_returnsSamePose() {
134         val underTest = Pose(translation = Vector3(1f, 2f, 3f))
135         val translation = Vector3.Zero
136 
137         val translatedPose = underTest.translate(translation)
138 
139         assertTranslation(translatedPose.translation, 1f, 2f, 3f)
140         assertThat(translatedPose.rotation).isEqualTo(underTest.rotation)
141     }
142 
143     @Test
translate_withVector3_returnsTranslatedPosenull144     fun translate_withVector3_returnsTranslatedPose() {
145         val underTest = Pose(translation = Vector3(1f, 2f, 3f))
146         val translation = Vector3(1f, 1f, 1f)
147 
148         val translatedPose = underTest.translate(translation)
149 
150         assertTranslation(translatedPose.translation, 2f, 3f, 4f)
151         assertThat(translatedPose.rotation).isEqualTo(underTest.rotation)
152     }
153 
154     @Test
rotate_withIdentityQuaternion_returnsSamePosenull155     fun rotate_withIdentityQuaternion_returnsSamePose() {
156         val underTest =
157             Pose(translation = Vector3(1f, 2f, 3f), rotation = Quaternion(1f, 2f, 3f, 4f))
158         val rotation = Quaternion.Identity
159 
160         val rotatedPose = underTest.rotate(rotation)
161 
162         assertTranslation(rotatedPose.translation, 1f, 2f, 3f)
163         assertRotation(
164             rotatedPose.rotation,
165             underTest.rotation.x,
166             underTest.rotation.y,
167             underTest.rotation.z,
168             underTest.rotation.w,
169         )
170     }
171 
172     @Test
rotate_withQuaternion_returnsRotatedPosenull173     fun rotate_withQuaternion_returnsRotatedPose() {
174         val underTest = Pose(rotation = Quaternion.Identity)
175         val rotation = Quaternion.fromAxisAngle(Vector3.Forward, 180f)
176 
177         val rotatedPose = underTest.rotate(rotation)
178 
179         assertThat(rotatedPose.translation).isEqualTo(underTest.translation)
180         assertRotation(rotatedPose.rotation, rotation.x, rotation.y, rotation.z, rotation.w)
181     }
182 
183     @Test
rotate_withNonIdentityQuaternion_returnsRotatedPosenull184     fun rotate_withNonIdentityQuaternion_returnsRotatedPose() {
185         // A pose with a rotation of 45 degrees around the Y-axis.
186         val underTest = Pose(rotation = Quaternion(0f, sqrt(2f) / 2, 0f, sqrt(2f) / 2))
187         // A Quaternion representing a 45-degree rotation around the Y-axis.
188         val rotation = Quaternion(0f, sqrt(2f) / 2, 0f, sqrt(2f) / 2)
189 
190         val rotatedPose = underTest.rotate(rotation)
191 
192         assertThat(rotatedPose.translation).isEqualTo(underTest.translation)
193         // The rotation of the rotated Pose is equal to a 90-degree rotation around the Y-axis.
194         assertRotation(rotatedPose.rotation, 0f, 1f, 0f, 0f)
195     }
196 
197     @Test
inverse_returnsPoseWithOppositeTransformationnull198     fun inverse_returnsPoseWithOppositeTransformation() {
199         val underTest =
200             Pose(
201                 translation = Vector3(3f, 0f, 0f),
202                 rotation = Quaternion(0f, sqrt(2f) / 2, 0f, sqrt(2f) / 2),
203             )
204 
205         val underTestInverted = underTest.inverse
206 
207         assertTranslation(underTestInverted.translation, 0f, 0f, -3f)
208         assertRotation(underTestInverted.rotation, 0f, -sqrt(2f) / 2, 0f, sqrt(2f) / 2)
209     }
210 
211     @Test
transform_returnsTransformedPointByPosenull212     fun transform_returnsTransformedPointByPose() {
213         val underTest =
214             Pose(translation = Vector3(0f, 0f, 1f), rotation = Quaternion(0f, 0.7071f, 0f, 0.7071f))
215         val point = Vector3(0f, 0f, 1f)
216 
217         val transformedPoint = underTest.transformPoint(point)
218 
219         assertTranslation(transformedPoint, 1f, 0f, 1f)
220     }
221 
222     @Test
lerp_returnsInterpolatedPosenull223     fun lerp_returnsInterpolatedPose() {
224         val underTest =
225             Pose(translation = Vector3(1f, 2f, 3f), rotation = Quaternion(4f, 5f, 6f, 7f))
226         val underTest2 =
227             Pose(translation = Vector3(4f, 5f, 6f), rotation = Quaternion(8f, 9f, 10f, 11f))
228 
229         val interpolatedPose = Pose.lerp(underTest, underTest2, 0.5f)
230 
231         assertTranslation(interpolatedPose.translation, 2.5f, 3.5f, 4.5f)
232         assertRotation(interpolatedPose.rotation, 0.38759f, 0.45833f, 0.52907f, 0.59981f)
233     }
234 
235     @Test
up_returnsUpVectorInLocalCoordinateSystem1null236     fun up_returnsUpVectorInLocalCoordinateSystem1() {
237         val underTest =
238             Pose(translation = Vector3(1f, 2f, 3f), rotation = Quaternion(0f, 0.7071f, 0f, 0.7071f))
239 
240         assertTranslation(underTest.up, 0f, 1f, 0f)
241     }
242 
243     @Test
up_returnsUpVectorInLocalCoordinateSystem2null244     fun up_returnsUpVectorInLocalCoordinateSystem2() {
245         val underTest =
246             Pose(translation = Vector3(1f, 2f, 3f), rotation = Quaternion(0.7071f, 0f, 0f, 0.7071f))
247 
248         assertTranslation(underTest.up, 0f, 0f, 1f)
249     }
250 
251     @Test
down_returnsDownVectorInLocalCoordinateSystem1null252     fun down_returnsDownVectorInLocalCoordinateSystem1() {
253         val underTest =
254             Pose(translation = Vector3(1f, 2f, 3f), rotation = Quaternion(0f, 0.7071f, 0f, 0.7071f))
255 
256         assertTranslation(underTest.down, 0f, -1f, 0f)
257     }
258 
259     @Test
down_returnsDownVectorInLocalCoordinateSystem2null260     fun down_returnsDownVectorInLocalCoordinateSystem2() {
261         val underTest =
262             Pose(translation = Vector3(1f, 2f, 3f), rotation = Quaternion(0.7071f, 0f, 0f, 0.7071f))
263 
264         assertTranslation(underTest.down, 0f, 0f, -1f)
265     }
266 
267     @Test
left_returnsLeftVectorInLocalCoordinateSystem1null268     fun left_returnsLeftVectorInLocalCoordinateSystem1() {
269         val underTest =
270             Pose(translation = Vector3(1f, 2f, 3f), rotation = Quaternion(0.7071f, 0f, 0f, 0.7071f))
271 
272         assertTranslation(underTest.left, -1f, 0f, 0f)
273     }
274 
275     @Test
left_returnsLeftVectorInLocalCoordinateSystem2null276     fun left_returnsLeftVectorInLocalCoordinateSystem2() {
277         val underTest =
278             Pose(translation = Vector3(1f, 2f, 3f), rotation = Quaternion(0f, 0.7071f, 0f, 0.7071f))
279 
280         assertTranslation(underTest.left, 0f, 0f, 1f)
281     }
282 
283     @Test
right_returnsRightVectorInLocalCoordinateSystem1null284     fun right_returnsRightVectorInLocalCoordinateSystem1() {
285         val underTest =
286             Pose(translation = Vector3(1f, 2f, 3f), rotation = Quaternion(0.7071f, 0f, 0f, 0.7071f))
287 
288         assertTranslation(underTest.right, 1f, 0f, 0f)
289     }
290 
291     @Test
right_returnsRightVectorInLocalCoordinateSystem2null292     fun right_returnsRightVectorInLocalCoordinateSystem2() {
293         val underTest =
294             Pose(translation = Vector3(1f, 2f, 3f), rotation = Quaternion(0f, 0.7071f, 0f, 0.7071f))
295 
296         assertTranslation(underTest.right, 0f, 0f, -1f)
297     }
298 
299     @Test
forward_returnsForwardVectorInLocalCoordinateSystem1null300     fun forward_returnsForwardVectorInLocalCoordinateSystem1() {
301         val underTest =
302             Pose(translation = Vector3(1f, 2f, 3f), rotation = Quaternion(0f, 0f, 0.7071f, 0.7071f))
303 
304         assertTranslation(underTest.forward, 0f, 0f, -1f)
305     }
306 
307     @Test
forward_returnsForwardVectorInLocalCoordinateSystem2null308     fun forward_returnsForwardVectorInLocalCoordinateSystem2() {
309         val underTest =
310             Pose(translation = Vector3(1f, 2f, 3f), rotation = Quaternion(0.7071f, 0f, 0f, 0.7071f))
311 
312         assertTranslation(underTest.forward, 0f, 1f, 0f)
313     }
314 
315     @Test
backward_returnsBackwardVectorInLocalCoordinateSystem1null316     fun backward_returnsBackwardVectorInLocalCoordinateSystem1() {
317         val underTest =
318             Pose(translation = Vector3(1f, 2f, 3f), rotation = Quaternion(0f, 0f, 0.7071f, 0.7071f))
319 
320         assertTranslation(underTest.backward, 0f, 0f, 1f)
321     }
322 
323     @Test
backward_returnsBackwardVectorInLocalCoordinateSystem2null324     fun backward_returnsBackwardVectorInLocalCoordinateSystem2() {
325         val underTest =
326             Pose(translation = Vector3(1f, 2f, 3f), rotation = Quaternion(0.7071f, 0f, 0f, 0.7071f))
327 
328         assertTranslation(underTest.backward, 0f, -1f, 0f)
329     }
330 
331     @Test
transformVector_returnsVectorTransformedByPosenull332     fun transformVector_returnsVectorTransformedByPose() {
333         val underTest =
334             Pose(translation = Vector3(1f, 2f, 3f), rotation = Quaternion(1f, 2f, 3f, 4f))
335         val vector = Vector3(1f, 2f, 3f)
336         val underTestRotated = underTest.transformVector(vector)
337 
338         assertTranslation(underTestRotated, 1f, 2f, 3f)
339     }
340 
341     @Test
copy_returnsCopyOfPosenull342     fun copy_returnsCopyOfPose() {
343         val underTest =
344             Pose(translation = Vector3(1f, 2f, 3f), rotation = Quaternion(4f, 5f, 6f, 7f))
345         val underTest2 = underTest.copy()
346 
347         assertThat(underTest).isEqualTo(underTest2)
348     }
349 
350     @Test
fromLookAt_returnsPoseLookingAtTargetnull351     fun fromLookAt_returnsPoseLookingAtTarget() {
352         val underTest =
353             Pose.fromLookAt(
354                 eye = Vector3.Zero,
355                 target = Vector3(0f, 0f, 10f),
356                 up = Vector3(0f, 1f, 0f)
357             )
358 
359         assertTranslation(underTest.translation, 0f, 0f, 0f)
360         assertRotation(underTest.rotation, 0f, 0f, 0f, 1f)
361     }
362 
assertTranslationnull363     private fun assertTranslation(
364         translation: Vector3,
365         expectedX: Float,
366         expectedY: Float,
367         expectedZ: Float,
368     ) {
369         assertThat(translation.x).isWithin(1.0e-4f).of(expectedX)
370         assertThat(translation.y).isWithin(1.0e-4f).of(expectedY)
371         assertThat(translation.z).isWithin(1.0e-4f).of(expectedZ)
372     }
373 
assertRotationnull374     private fun assertRotation(
375         rotation: Quaternion,
376         expectedX: Float,
377         expectedY: Float,
378         expectedZ: Float,
379         expectedW: Float,
380     ) {
381         assertThat(rotation.x).isWithin(1.0e-4f).of(expectedX)
382         assertThat(rotation.y).isWithin(1.0e-4f).of(expectedY)
383         assertThat(rotation.z).isWithin(1.0e-4f).of(expectedZ)
384         assertThat(rotation.w).isWithin(1.0e-4f).of(expectedW)
385     }
386 }
387