1 /*
2  * Copyright (C) 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.ink.geometry
18 
19 import com.google.common.truth.Truth.assertThat
20 import kotlin.math.sqrt
21 import kotlin.test.assertFailsWith
22 import org.junit.Test
23 import org.junit.runner.RunWith
24 import org.junit.runners.JUnit4
25 
26 @RunWith(JUnit4::class)
27 class VecTest {
28 
29     @Test
isAlmostEqual_whenNoToleranceGiven_returnsCorrectValuenull30     fun isAlmostEqual_whenNoToleranceGiven_returnsCorrectValue() {
31         val vec = ImmutableVec(1f, 2f)
32 
33         assertThat(vec.isAlmostEqual(vec)).isTrue()
34         assertThat(vec.isAlmostEqual(ImmutableVec(1f, 2f))).isTrue()
35         assertThat(vec.isAlmostEqual(ImmutableVec(1.00001f, 1.99999f))).isTrue()
36         assertThat(vec.isAlmostEqual(ImmutableVec(1f, 1.99f))).isFalse()
37         assertThat(vec.isAlmostEqual(ImmutableVec(1.01f, 2f))).isFalse()
38         assertThat(vec.isAlmostEqual(ImmutableVec(1.01f, 1.99f))).isFalse()
39     }
40 
41     @Test
isAlmostEqual_withToleranceGiven_returnsCorrectValuenull42     fun isAlmostEqual_withToleranceGiven_returnsCorrectValue() {
43         val vec = ImmutableVec(1f, 2f)
44 
45         assertThat(vec.isAlmostEqual(vec, tolerance = 0.00000001f)).isTrue()
46         assertThat(vec.isAlmostEqual(ImmutableVec(1f, 2f), tolerance = 0.00000001f)).isTrue()
47         assertThat(vec.isAlmostEqual(ImmutableVec(1.00001f, 1.99999f), tolerance = 0.000001f))
48             .isFalse()
49         assertThat(vec.isAlmostEqual(ImmutableVec(1f, 1.99f), tolerance = 0.02f)).isTrue()
50         assertThat(vec.isAlmostEqual(ImmutableVec(1.01f, 2f), tolerance = 0.02f)).isTrue()
51         assertThat(vec.isAlmostEqual(ImmutableVec(1.01f, 1.99f), tolerance = 0.02f)).isTrue()
52         assertThat(vec.isAlmostEqual(ImmutableVec(2.5f, 0.5f), tolerance = 2f)).isTrue()
53     }
54 
55     @Test
isAlmostEqual_whenSameInterface_returnsTruenull56     fun isAlmostEqual_whenSameInterface_returnsTrue() {
57         val vec = MutableVec(1f, 2f)
58         val other = ImmutableVec(0.99999f, 2.00001f)
59         assertThat(vec.isAlmostEqual(other)).isTrue()
60     }
61 
62     @Test
direction_returnsCorrectValuenull63     fun direction_returnsCorrectValue() {
64         assertThat(ImmutableVec(5f, 0f).computeDirection()).isEqualTo(Angle.degreesToRadians(0f))
65         assertThat(ImmutableVec(0f, 5f).computeDirection()).isEqualTo(Angle.degreesToRadians(90f))
66         assertThat(ImmutableVec(-5f, 0f).computeDirection()).isEqualTo(Angle.degreesToRadians(180f))
67         assertThat(ImmutableVec(0f, -5f).computeDirection()).isEqualTo(Angle.degreesToRadians(-90f))
68         assertThat(ImmutableVec(5f, 5f).computeDirection()).isEqualTo(Angle.degreesToRadians(45f))
69         assertThat(ImmutableVec(-5f, 5f).computeDirection()).isEqualTo(Angle.degreesToRadians(135f))
70         assertThat(ImmutableVec(-5f, -5f).computeDirection())
71             .isEqualTo(Angle.degreesToRadians(-135f))
72         assertThat(ImmutableVec(5f, -5f).computeDirection()).isEqualTo(Angle.degreesToRadians(-45f))
73     }
74 
75     @Test
direction_whenVecContainsZero_returnsCorrectValuenull76     fun direction_whenVecContainsZero_returnsCorrectValue() {
77         assertThat(ImmutableVec(+0f, +0f).computeDirection()).isEqualTo(Angle.degreesToRadians(0f))
78         assertThat(ImmutableVec(+0f, -0f).computeDirection()).isEqualTo(Angle.degreesToRadians(-0f))
79         assertThat(ImmutableVec(-0f, +0f).computeDirection())
80             .isEqualTo(Angle.degreesToRadians(180f))
81         assertThat(ImmutableVec(-0f, -0f).computeDirection())
82             .isEqualTo(Angle.degreesToRadians(-180f))
83     }
84 
85     @Test
unitVec_returnsCorrectValuenull86     fun unitVec_returnsCorrectValue() {
87         assertThat(ImmutableVec(4f, 0f).computeUnitVec()).isEqualTo(ImmutableVec(1f, 0f))
88         assertThat(MutableVec(0f, -25f).computeUnitVec()).isEqualTo(ImmutableVec(0f, -1f))
89         assertThat(
90                 ImmutableVec(30f, 30f)
91                     .computeUnitVec()
92                     .isAlmostEqual(ImmutableVec(sqrt(.5f), sqrt(.5f)), tolerance = 0.000001f)
93             )
94             .isTrue()
95         assertThat(
96                 MutableVec(-.05f, -.05f)
97                     .computeUnitVec()
98                     .isAlmostEqual(ImmutableVec(-sqrt(.5f), -sqrt(.5f)), tolerance = 0.000001f)
99             )
100             .isTrue()
101     }
102 
103     @Test
unitVec_whenVecContainsZeroes_returnsCorrectValuenull104     fun unitVec_whenVecContainsZeroes_returnsCorrectValue() {
105         assertThat(ImmutableVec(+0f, 0f).computeUnitVec()).isEqualTo(ImmutableVec(1f, 0f))
106         assertThat(MutableVec(-0f, 0f).computeUnitVec()).isEqualTo(ImmutableVec(-1f, 0f))
107     }
108 
109     @Test
populateUnitVec_populatesCorrectValuenull110     fun populateUnitVec_populatesCorrectValue() {
111         val mutableVec = MutableVec(0f, 0f)
112         MutableVec(4f, 0f).computeUnitVec(mutableVec)
113         assertThat(mutableVec).isEqualTo(ImmutableVec(1f, 0f))
114 
115         ImmutableVec(0f, -25f).computeUnitVec(mutableVec)
116         assertThat(mutableVec).isEqualTo(ImmutableVec(0f, -1f))
117 
118         MutableVec(30f, 30f).computeUnitVec(mutableVec)
119         assertThat(mutableVec.isAlmostEqual(ImmutableVec(sqrt(.5f), sqrt(.5f)))).isTrue()
120 
121         ImmutableVec(-.05f, -.05f).computeUnitVec(mutableVec)
122         assertThat(mutableVec.isAlmostEqual(ImmutableVec(-sqrt(.5f), -sqrt(.5f)))).isTrue()
123     }
124 
125     @Test
populateUnitVec_whenVecContainsZeroes_populatesCorrectValuenull126     fun populateUnitVec_whenVecContainsZeroes_populatesCorrectValue() {
127         val mutableVec = MutableVec(0f, 0f)
128         MutableVec(+0f, 0f).computeUnitVec(mutableVec)
129         assertThat(mutableVec).isEqualTo(ImmutableVec(1f, 0f))
130 
131         ImmutableVec(-0f, -0f).computeUnitVec(mutableVec)
132         assertThat(mutableVec).isEqualTo(ImmutableVec(-1f, 0f))
133     }
134 
135     @Test
absoluteAngleBetween_returnsCorrectValuenull136     fun absoluteAngleBetween_returnsCorrectValue() {
137         assertThat(Vec.absoluteAngleBetween(ImmutableVec(10f, 0f), ImmutableVec(40f, 0f)))
138             .isEqualTo(Angle.degreesToRadians(0f))
139         assertThat(Vec.absoluteAngleBetween(MutableVec(7f, 0f), MutableVec(0f, 12f)))
140             .isEqualTo(Angle.degreesToRadians(90f))
141         assertThat(Vec.absoluteAngleBetween(ImmutableVec(-5f, 0f), MutableVec(.1f, 0f)))
142             .isEqualTo(Angle.degreesToRadians(180f))
143         assertThat(Vec.absoluteAngleBetween(MutableVec(20f, 20f), ImmutableVec(0f, 10f)))
144             .isEqualTo(Angle.degreesToRadians(45f))
145         assertThat(Vec.absoluteAngleBetween(ImmutableVec(-2f, 2f), ImmutableVec(0f, -3f)))
146             .isEqualTo(Angle.degreesToRadians(135f))
147         assertThat(
148                 Vec.absoluteAngleBetween(MutableVec(-1f, -sqrt(3.0f)), MutableVec(1f, -sqrt(3.0f)))
149             )
150             .isEqualTo(Angle.degreesToRadians(60f))
151     }
152 
153     @Test
signedAngleBetween_returnsCorrectValuenull154     fun signedAngleBetween_returnsCorrectValue() {
155         assertThat(Vec.signedAngleBetween(MutableVec(2f, 0f), MutableVec(2f, 0f)))
156             .isEqualTo(Angle.degreesToRadians(0f))
157         assertThat(Vec.signedAngleBetween(ImmutableVec(20f, 0f), ImmutableVec(0f, .1f)))
158             .isEqualTo(Angle.degreesToRadians(90f))
159         assertThat(Vec.signedAngleBetween(MutableVec(0f, 10f), ImmutableVec(17f, 0f)))
160             .isEqualTo(Angle.degreesToRadians(-90f))
161         assertThat(Vec.signedAngleBetween(ImmutableVec(-1f, 0f), MutableVec(.11f, 0f)))
162             .isEqualTo(Angle.degreesToRadians(180f))
163         assertThat(Vec.signedAngleBetween(MutableVec(12f, 12f), MutableVec(-3f, 3f)))
164             .isEqualTo(Angle.degreesToRadians(90f))
165         assertThat(Vec.signedAngleBetween(ImmutableVec(-1f, -1f), ImmutableVec(-987f, 0f)))
166             .isEqualTo(Angle.degreesToRadians(-45f))
167         assertThat(Vec.signedAngleBetween(ImmutableVec(-62f, -62f), ImmutableVec(sqrt(3.0f), 1f)))
168             .isEqualTo(Angle.degreesToRadians(165f))
169         assertThat(Vec.signedAngleBetween(MutableVec(-11f, 11f), ImmutableVec(.01f, 0f)))
170             .isEqualTo(Angle.degreesToRadians(-135f))
171         assertThat(
172                 Vec.signedAngleBetween(MutableVec(1f, -sqrt(3.0f)), MutableVec(-1f, -sqrt(3.0f)))
173             )
174             .isEqualTo(Angle.degreesToRadians(-60f))
175     }
176 
177     @Test
isParallelTo_withEquivalentVecs_returnsTruenull178     fun isParallelTo_withEquivalentVecs_returnsTrue() {
179         assertThat(MutableVec(1f, 0f).isParallelTo(MutableVec(1f, 0f), .001f)).isTrue()
180         assertThat(MutableVec(0f, 100f).isParallelTo(MutableVec(0f, 100f), .001f)).isTrue()
181         assertThat(MutableVec(359.38f, -7.84f).isParallelTo(MutableVec(359.38f, -7.84f), .001f))
182             .isTrue()
183     }
184 
185     @Test
isParallelTo_whenVecsHaveSameDirection_returnsTruenull186     fun isParallelTo_whenVecsHaveSameDirection_returnsTrue() {
187         assertThat(MutableVec(10f, 0f).isParallelTo(MutableVec(99f, 0f), .001f)).isTrue()
188         assertThat(MutableVec(0f, 40f).isParallelTo(MutableVec(0f, 99f), .001f)).isTrue()
189         assertThat(MutableVec(3f, -6f).isParallelTo(MutableVec(32f, -64f), .001f)).isTrue()
190         assertThat(MutableVec(.0001f, .0009f).isParallelTo(MutableVec(.0005f, .0045f), .001f))
191             .isTrue()
192     }
193 
194     @Test
isParallelTo_whenVecsHaveOppositeDirections_returnsTruenull195     fun isParallelTo_whenVecsHaveOppositeDirections_returnsTrue() {
196         assertThat(MutableVec(8f, 0f).isParallelTo(MutableVec(-7f, 0f), .001f)).isTrue()
197         assertThat(MutableVec(0f, 30f).isParallelTo(MutableVec(0f, -.99f), .001f)).isTrue()
198         assertThat(MutableVec(.2f, .2f).isParallelTo(MutableVec(-99f, -99f), .001f)).isTrue()
199         assertThat(MutableVec(-32f, 64f).isParallelTo(MutableVec(5f, -10f), .001f)).isTrue()
200     }
201 
202     @Test
isParallelTo_whenVecsHaveDifferentDirections_returnsFalsenull203     fun isParallelTo_whenVecsHaveDifferentDirections_returnsFalse() {
204         assertThat(MutableVec(5f, 5f).isParallelTo(MutableVec(1f, -1f), .001f)).isFalse()
205         assertThat(MutableVec(-3f, -10f).isParallelTo(MutableVec(-88f, 17.5f), .001f)).isFalse()
206 
207         // These Vecs have different but close directions. These would pass with sufficiently high
208         // tolerance, but fail with low tolerance.
209         assertThat(MutableVec(100f, 100f).isParallelTo(MutableVec(99f, 100f), .001f)).isFalse()
210         assertThat(MutableVec(100f, 100f).isParallelTo(MutableVec(100f, 99f), .001f)).isFalse()
211         assertThat(MutableVec(-100f, 100f).isParallelTo(MutableVec(-99f, 100f), .001f)).isFalse()
212         assertThat(MutableVec(100f, -100f).isParallelTo(MutableVec(100f, -99f), .001f)).isFalse()
213     }
214 
215     @Test
isPerpendicularTo_returnsCorrectValuenull216     fun isPerpendicularTo_returnsCorrectValue() {
217         assertThat(MutableVec(1f, 0f).isPerpendicularTo(MutableVec(0f, 5f), .001f)).isTrue()
218         assertThat(MutableVec(5f, 0f).isPerpendicularTo(MutableVec(0f, -10f), .001f)).isTrue()
219         assertThat(MutableVec(0f, 100f).isPerpendicularTo(MutableVec(-.01f, 0f), .001f)).isTrue()
220         assertThat(MutableVec(77f, -77f).isPerpendicularTo(MutableVec(200f, 200f), .001f)).isTrue()
221         assertThat(MutableVec(-32f, 64f).isPerpendicularTo(MutableVec(86f, 43f), .001f)).isTrue()
222         assertThat(
223                 MutableVec(.0001f, -.0009f).isPerpendicularTo(MutableVec(-.0045f, -.0005f), .001f)
224             )
225             .isTrue()
226 
227         assertThat(MutableVec(1f, -2f).isPerpendicularTo(MutableVec(1f, -2f), .001f)).isFalse()
228         assertThat(MutableVec(1f, -2f).isPerpendicularTo(MutableVec(-1f, 2f), .001f)).isFalse()
229         assertThat(MutableVec(10f, 10f).isPerpendicularTo(MutableVec(0f, 10f), .001f)).isFalse()
230         assertThat(MutableVec(-30f, 25f).isPerpendicularTo(MutableVec(50f, 30f), .001f)).isFalse()
231 
232         // These Vecs are close but not quite perpendicular. These would pass with sufficiently high
233         // tolerance, but fail with low tolerance.
234         assertThat(MutableVec(100f, 100f).isPerpendicularTo(MutableVec(-99f, 100f), .001f))
235             .isFalse()
236         assertThat(MutableVec(100f, 100f).isPerpendicularTo(MutableVec(-100f, 99f), .001f))
237             .isFalse()
238         assertThat(MutableVec(-100f, 100f).isPerpendicularTo(MutableVec(-99f, -100f), .001f))
239             .isFalse()
240         assertThat(MutableVec(100f, -100f).isPerpendicularTo(MutableVec(100f, 99f), .001f))
241             .isFalse()
242     }
243 
244     @Test
determinant_returnsCorrectValuenull245     fun determinant_returnsCorrectValue() {
246         val a = ImmutableVec(3f, 0f)
247         val b = ImmutableVec(-1f, 4f)
248         val c = ImmutableVec(2f, .5f)
249 
250         assertThat(Vec.determinant(a, b)).isEqualTo(12f)
251         assertThat(Vec.determinant(a, c)).isEqualTo(1.5f)
252         assertThat(Vec.determinant(b, a)).isEqualTo(-12f)
253         assertThat(Vec.determinant(b, c)).isEqualTo(-8.5f)
254         assertThat(Vec.determinant(c, a)).isEqualTo(-1.5f)
255         assertThat(Vec.determinant(c, b)).isEqualTo(8.5f)
256     }
257 
258     @Test
add_populatesCorrectValuenull259     fun add_populatesCorrectValue() {
260         val a = ImmutableVec(3f, 0f)
261         val b = MutableVec(-1f, .3f)
262         val c = ImmutableVec(2.7f, 4f)
263 
264         val aPlusbOut = MutableVec()
265         val aPluscOut = MutableVec()
266         val bPluscOut = MutableVec()
267 
268         Vec.add(a, b, aPlusbOut)
269         Vec.add(a, c, aPluscOut)
270         Vec.add(b, c, bPluscOut)
271 
272         assertThat(aPlusbOut.isAlmostEqual(ImmutableVec(2f, .3f))).isTrue()
273         assertThat(aPluscOut.isAlmostEqual(ImmutableVec(5.7f, 4f))).isTrue()
274         assertThat(bPluscOut.isAlmostEqual(ImmutableVec(1.7f, 4.3f))).isTrue()
275     }
276 
277     @Test
multiply_populatesCorrectValuenull278     fun multiply_populatesCorrectValue() {
279         val a = ImmutableVec(.7f, -3f)
280         val b = MutableVec(3f, 5f)
281 
282         val aMultipliedBy2Out = MutableVec()
283         val aMultipliedBy1TenthOut = MutableVec()
284         val bMultipliedBy4Out = MutableVec()
285         val bMultipliedByNegative3TenthsOut = MutableVec()
286 
287         Vec.multiply(a, 2f, aMultipliedBy2Out)
288         Vec.multiply(.1f, a, aMultipliedBy1TenthOut)
289         Vec.multiply(b, 4f, bMultipliedBy4Out)
290         Vec.multiply(-.3f, b, bMultipliedByNegative3TenthsOut)
291 
292         assertThat(aMultipliedBy2Out.isAlmostEqual(ImmutableVec(1.4f, -6f))).isTrue()
293         assertThat(aMultipliedBy1TenthOut.isAlmostEqual(ImmutableVec(.07f, -0.3f))).isTrue()
294         assertThat(bMultipliedBy4Out.isAlmostEqual(ImmutableVec(12f, 20f))).isTrue()
295         assertThat(bMultipliedByNegative3TenthsOut.isAlmostEqual(ImmutableVec(-0.9f, -1.5f)))
296             .isTrue()
297     }
298 
299     @Test
divide_populatesCorrectValuenull300     fun divide_populatesCorrectValue() {
301         val a = ImmutableVec(7f, .9f)
302         val b = MutableVec(-4.5f, -2f)
303 
304         val aDividedBy2Out = MutableVec()
305         val aDividedByNegative1TenthOut = MutableVec()
306         val bDividedBy5Out = MutableVec()
307         val bDividedBy2TenthsOut = MutableVec()
308 
309         Vec.divide(a, 2f, aDividedBy2Out)
310         Vec.divide(a, -.1f, aDividedByNegative1TenthOut)
311         Vec.divide(b, 5f, bDividedBy5Out)
312         Vec.divide(b, .2f, bDividedBy2TenthsOut)
313 
314         assertThat(aDividedBy2Out.isAlmostEqual(ImmutableVec(3.5f, .45f))).isTrue()
315         assertThat(aDividedByNegative1TenthOut.isAlmostEqual(ImmutableVec(-70f, -9f))).isTrue()
316         assertThat(bDividedBy5Out.isAlmostEqual(ImmutableVec(-.9f, -.4f))).isTrue()
317         assertThat(bDividedBy2TenthsOut.isAlmostEqual(ImmutableVec(-22.5f, -10f))).isTrue()
318     }
319 
320     @Test
divide_whenDividingByZero_throwsExceptionnull321     fun divide_whenDividingByZero_throwsException() {
322         val testOutput = MutableVec()
323 
324         assertFailsWith<IllegalArgumentException> {
325             Vec.divide(ImmutableVec(2f, 3f), 0f, testOutput)
326         }
327         assertFailsWith<IllegalArgumentException> { Vec.divide(MutableVec(0f, 0f), 0f, testOutput) }
328     }
329 
330     @Test
subtract_returnsCorrectValuenull331     fun subtract_returnsCorrectValue() {
332         val a = ImmutableVec(0f, -2f)
333         val b = MutableVec(.5f, 19f)
334         val c = ImmutableVec(1.1f, -3.4f)
335         val aMinusbOut = MutableVec()
336         val aMinuscOut = MutableVec()
337         val bMinuscOut = MutableVec()
338 
339         Vec.subtract(a, b, aMinusbOut)
340         Vec.subtract(a, c, aMinuscOut)
341         Vec.subtract(b, c, bMinuscOut)
342 
343         assertThat(aMinusbOut.isAlmostEqual(ImmutableVec(-.5f, -21f), tolerance = 0.001f)).isTrue()
344         assertThat(aMinuscOut.isAlmostEqual(ImmutableVec(-1.1f, 1.4f), tolerance = 0.001f)).isTrue()
345         assertThat(bMinuscOut.isAlmostEqual(ImmutableVec(-.6f, 22.4f), tolerance = 0.001f)).isTrue()
346     }
347 
348     @Test
dotProduct_returnsCorrectValuenull349     fun dotProduct_returnsCorrectValue() {
350         val a = ImmutableVec(3f, 0f)
351         val b = MutableVec(-1f, 4f)
352         val c = MutableVec(2f, .5f)
353         val d = ImmutableVec(6f, 6f)
354 
355         assertThat(Vec.dotProduct(a, b)).isEqualTo(-3f)
356         assertThat(Vec.dotProduct(a, c)).isEqualTo(6f)
357         assertThat(Vec.dotProduct(a, d)).isEqualTo(18f)
358         assertThat(Vec.dotProduct(b, c)).isEqualTo(0f)
359         assertThat(Vec.dotProduct(b, d)).isEqualTo(18f)
360         assertThat(Vec.dotProduct(c, d)).isEqualTo(15f)
361     }
362 
363     @Test
origin_isCorrectValueAndReturnsSameInstancenull364     fun origin_isCorrectValueAndReturnsSameInstance() {
365         assertThat(Vec.ORIGIN).isEqualTo(ImmutableVec(0f, 0f))
366         assertThat(Vec.ORIGIN).isSameInstanceAs(Vec.ORIGIN)
367     }
368 }
369