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