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.strokes
18 
19 import androidx.ink.brush.InputToolType
20 import androidx.ink.geometry.AffineTransform
21 import androidx.ink.geometry.ImmutableBox
22 import androidx.ink.geometry.ImmutableVec
23 import androidx.ink.geometry.Intersection.intersects
24 import com.google.common.truth.Truth.assertThat
25 import kotlin.collections.listOf
26 import kotlin.test.assertFailsWith
27 import org.junit.Test
28 import org.junit.runner.RunWith
29 import org.junit.runners.JUnit4
30 
31 @RunWith(JUnit4::class)
32 class MeshCreationTest {
33 
createStrokeInputBatchnull34     private fun createStrokeInputBatch(points: List<ImmutableVec>): StrokeInputBatch {
35         val strokeInputBatch = MutableStrokeInputBatch()
36         var count = 0
37         for (point in points) {
38             strokeInputBatch.addOrThrow(
39                 type = InputToolType.STYLUS,
40                 x = point.x,
41                 y = point.y,
42                 elapsedTimeMillis = 5L * count++,
43             )
44         }
45         return strokeInputBatch
46     }
47 
48     @Test
createClosedShapeFromStrokeInputBatch_square_intersectsCorrectPointsnull49     fun createClosedShapeFromStrokeInputBatch_square_intersectsCorrectPoints() {
50         val strokeInputBatch =
51             createStrokeInputBatch(
52                 listOf(
53                     ImmutableVec(1f, 1f),
54                     ImmutableVec(9f, 1f),
55                     ImmutableVec(9f, 9f),
56                     ImmutableVec(1f, 9f),
57                     ImmutableVec(1f, 1f),
58                 )
59             )
60 
61         val mesh = strokeInputBatch.createClosedShape()
62 
63         assertThat(mesh.intersects(ImmutableVec(2f, 2f), AffineTransform.IDENTITY)).isTrue()
64         assertThat(mesh.intersects(ImmutableVec(4f, 2f), AffineTransform.IDENTITY)).isTrue()
65         assertThat(mesh.intersects(ImmutableVec(4f, 4f), AffineTransform.IDENTITY)).isTrue()
66         assertThat(mesh.intersects(ImmutableVec(2f, 4f), AffineTransform.IDENTITY)).isTrue()
67 
68         assertThat(mesh.intersects(ImmutableVec(0f, 0f), AffineTransform.IDENTITY)).isFalse()
69         assertThat(mesh.intersects(ImmutableVec(10f, 0f), AffineTransform.IDENTITY)).isFalse()
70         assertThat(mesh.intersects(ImmutableVec(10f, 10f), AffineTransform.IDENTITY)).isFalse()
71         assertThat(mesh.intersects(ImmutableVec(0f, 10f), AffineTransform.IDENTITY)).isFalse()
72     }
73 
74     @Test
createClosedShapeFromStrokeInputBatch_triangle_intersectsCorrectPointsnull75     fun createClosedShapeFromStrokeInputBatch_triangle_intersectsCorrectPoints() {
76         val strokeInputBatch =
77             createStrokeInputBatch(
78                 listOf(
79                     ImmutableVec(-1f, -1f),
80                     ImmutableVec(-90f, -90f),
81                     ImmutableVec(-90f, -1f),
82                     ImmutableVec(-1f, -1f),
83                 )
84             )
85 
86         val mesh = strokeInputBatch.createClosedShape()
87 
88         assertThat(mesh.intersects(ImmutableVec(-3f, -1.5f), AffineTransform.IDENTITY)).isTrue()
89         assertThat(mesh.intersects(ImmutableVec(-85f, -50f), AffineTransform.IDENTITY)).isTrue()
90         assertThat(mesh.intersects(ImmutableVec(-89f, -1.1f), AffineTransform.IDENTITY)).isTrue()
91         assertThat(mesh.intersects(ImmutableVec(-9f, -8f), AffineTransform.IDENTITY)).isTrue()
92 
93         assertThat(mesh.intersects(ImmutableVec(0f, 0f), AffineTransform.IDENTITY)).isFalse()
94         assertThat(mesh.intersects(ImmutableVec(5f, -2f), AffineTransform.IDENTITY)).isFalse()
95         assertThat(mesh.intersects(ImmutableVec(-5f, 2f), AffineTransform.IDENTITY)).isFalse()
96         assertThat(mesh.intersects(ImmutableVec(-91f, -10f), AffineTransform.IDENTITY)).isFalse()
97     }
98 
99     @Test
createClosedShapeFromStrokeInputBatch_collinearPoints_throwsIllegalStateExceptionnull100     fun createClosedShapeFromStrokeInputBatch_collinearPoints_throwsIllegalStateException() {
101         val strokeInputBatch =
102             createStrokeInputBatch(
103                 listOf(
104                     ImmutableVec(-1f, -1f),
105                     ImmutableVec(0f, 0f),
106                     ImmutableVec(1f, 1f),
107                     ImmutableVec(2f, 2f),
108                     ImmutableVec(3f, 3f),
109                 )
110             )
111 
112         assertFailsWith(IllegalStateException::class) { strokeInputBatch.createClosedShape() }
113     }
114 
115     @Test
createClosedShapeFromStrokeInputBatch_onePoint_createsPointLikeMeshnull116     fun createClosedShapeFromStrokeInputBatch_onePoint_createsPointLikeMesh() {
117         val strokeInputBatch = createStrokeInputBatch(listOf(ImmutableVec(-90f, -90f)))
118 
119         val mesh = strokeInputBatch.createClosedShape()
120 
121         assertThat(mesh.intersects(ImmutableVec(-90f, -90f), AffineTransform.IDENTITY)).isTrue()
122         assertThat(mesh.computeBoundingBox())
123             .isEqualTo(
124                 ImmutableBox.fromTwoPoints(ImmutableVec(-90f, -90f), ImmutableVec(-90f, -90f))
125             )
126     }
127 
128     @Test
createClosedShapeFromStrokeInputBatch_manyIdentiticalPoints_createsPointLikeMeshnull129     fun createClosedShapeFromStrokeInputBatch_manyIdentiticalPoints_createsPointLikeMesh() {
130         val strokeInputBatch =
131             createStrokeInputBatch(
132                 listOf(
133                     ImmutableVec(35f, 85f),
134                     ImmutableVec(35f, 85f),
135                     ImmutableVec(35f, 85f),
136                     ImmutableVec(35f, 85f),
137                     ImmutableVec(35f, 85f),
138                     ImmutableVec(35f, 85f),
139                     ImmutableVec(35f, 85f),
140                 )
141             )
142 
143         val mesh = strokeInputBatch.createClosedShape()
144 
145         assertThat(mesh.intersects(ImmutableVec(35f, 85f), AffineTransform.IDENTITY)).isTrue()
146         assertThat(mesh.computeBoundingBox())
147             .isEqualTo(ImmutableBox.fromTwoPoints(ImmutableVec(35f, 85f), ImmutableVec(35f, 85f)))
148     }
149 
150     @Test
createClosedShapeFromStrokeInputBatch_twoPoints_createsSegmentLikeMeshnull151     fun createClosedShapeFromStrokeInputBatch_twoPoints_createsSegmentLikeMesh() {
152         val strokeInputBatch =
153             createStrokeInputBatch(listOf(ImmutableVec(-1f, -1f), ImmutableVec(-1f, 99f)))
154 
155         val mesh = strokeInputBatch.createClosedShape()
156 
157         assertThat(mesh.intersects(ImmutableVec(-1f, -1f), AffineTransform.IDENTITY)).isTrue()
158         assertThat(mesh.intersects(ImmutableVec(-1f, 99f), AffineTransform.IDENTITY)).isTrue()
159         assertThat(mesh.intersects(ImmutableVec(-1f, 50f), AffineTransform.IDENTITY)).isTrue()
160         assertThat(mesh.computeBoundingBox())
161             .isEqualTo(ImmutableBox.fromTwoPoints(ImmutableVec(-1f, -1f), ImmutableVec(-1f, 99f)))
162     }
163 
164     @Test
createClosedShapeFromStrokeInputBatch_twoRepeatedPoints_createsSegmentLikeMeshnull165     fun createClosedShapeFromStrokeInputBatch_twoRepeatedPoints_createsSegmentLikeMesh() {
166         val strokeInputBatch =
167             createStrokeInputBatch(
168                 listOf(
169                     ImmutableVec(80f, 1f),
170                     ImmutableVec(80f, 1f),
171                     ImmutableVec(80f, 1f),
172                     ImmutableVec(80f, 1f),
173                     ImmutableVec(80f, 1f),
174                     ImmutableVec(1f, 1f),
175                     ImmutableVec(1f, 1f),
176                     ImmutableVec(1f, 1f),
177                     ImmutableVec(1f, 1f),
178                     ImmutableVec(1f, 1f),
179                     ImmutableVec(1f, 1f),
180                     ImmutableVec(1f, 1f),
181                     ImmutableVec(1f, 1f),
182                 )
183             )
184 
185         val mesh = strokeInputBatch.createClosedShape()
186 
187         assertThat(mesh.intersects(ImmutableVec(80f, 1f), AffineTransform.IDENTITY)).isTrue()
188         assertThat(mesh.intersects(ImmutableVec(40f, 1f), AffineTransform.IDENTITY)).isTrue()
189         assertThat(mesh.intersects(ImmutableVec(1f, 1f), AffineTransform.IDENTITY)).isTrue()
190         assertThat(mesh.computeBoundingBox())
191             .isEqualTo(ImmutableBox.fromTwoPoints(ImmutableVec(80f, 1f), ImmutableVec(1f, 1f)))
192     }
193 }
194