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.graphics.shapes 18 19 // TODO: b/372000685 b/372003785 b/372004969 20 // Make class public as soon as all validations 21 // are implemented and mention in [RoundedPolygon] constructor 22 23 /** 24 * Utility class to fix invalid [RoundedPolygon]s that will otherwise break [Morph]s in one way or 25 * another, as [RoundedPolygon] assumes correct input. Correct input meaning: 26 * - Closed geometry 27 * - Clockwise orientation of points 28 * - No self-intersections 29 * - No holes 30 * - Single Polygon 31 */ 32 internal class PolygonValidator() { 33 34 companion object { 35 36 // TODO: b/372000685 b/372003785 b/372004969 37 // Update docs when other validations are implemented 38 /** 39 * Validates whether this [RoundedPolygon]'s orientation is clockwise and fixes it if 40 * necessary. 41 * 42 * @param polygon The [RoundedPolygon] to validate 43 * @return A new [RoundedPolygon] with fixed orientation, or the same [RoundedPolygon] as 44 * given when it was already valid 45 */ fixnull46 fun fix(polygon: RoundedPolygon): RoundedPolygon { 47 var result = polygon 48 49 debugLog(LOG_TAG) { "Validating polygon..." } 50 51 if (isCWOriented(polygon)) { 52 debugLog(LOG_TAG) { "Passed clockwise validation!" } 53 } else { 54 debugLog(LOG_TAG) { "Polygon is oriented anti-clockwise, fixing orientation..." } 55 result = fixCWOrientation(polygon) 56 } 57 58 return result 59 } 60 isCWOrientednull61 private fun isCWOriented(polygon: RoundedPolygon): Boolean { 62 var signedArea = 0.0f 63 64 for (i in polygon.cubics.indices) { 65 val cubic = polygon.cubics[i] 66 signedArea += (cubic.anchor1X - cubic.anchor0X) * (cubic.anchor1Y + cubic.anchor0Y) 67 } 68 69 return signedArea < 0 70 } 71 fixCWOrientationnull72 private fun fixCWOrientation(polygon: RoundedPolygon): RoundedPolygon { 73 val reversedFeatures = buildList { 74 // Persist first feature to stay a Corner 75 add(polygon.features.first().reversed()) 76 77 for (i in polygon.features.lastIndex downTo 1) { 78 add(polygon.features[i].reversed()) 79 } 80 } 81 82 return RoundedPolygon(reversedFeatures, polygon.center) 83 } 84 } 85 } 86 87 private const val LOG_TAG = "PolygonValidation" 88