/*
* Copyright 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@file:JvmName("Utils")
package androidx.graphics.shapes
import android.graphics.PointF
import android.util.Log
import androidx.core.graphics.div
import androidx.core.graphics.plus
import androidx.core.graphics.times
import kotlin.math.atan2
import kotlin.math.cos
import kotlin.math.sin
import kotlin.math.sqrt
/**
* This class has all internal methods, used by Polygon, Morph, etc.
*/
internal fun interpolate(start: Float, stop: Float, fraction: Float) =
(start * (1 - fraction) + stop * fraction)
internal fun interpolate(start: PointF, stop: PointF, fraction: Float): PointF {
return PointF(
interpolate(start.x, stop.x, fraction),
interpolate(start.y, stop.y, fraction)
)
}
/**
* Non-allocating version of interpolate; values are stored in [result]
*/
internal fun interpolate(start: PointF, stop: PointF, result: PointF, fraction: Float): PointF {
result.x = interpolate(start.x, stop.x, fraction)
result.y = interpolate(start.y, stop.y, fraction)
return result
}
internal fun PointF.getDistance() = sqrt(x * x + y * y)
internal fun PointF.dotProduct(other: PointF) = x * other.x + y * other.y
/**
* Compute the Z coordinate of the cross product of two vectors, to check if the second vector is
* going clockwise ( > 0 ) or counterclockwise (< 0) compared with the first one.
* It could also be 0, if the vectors are co-linear.
*/
internal fun PointF.clockwise(other: PointF) = x * other.y - y * other.x > 0
/**
* Returns unit vector representing the direction to this point from (0, 0)
*/
internal fun PointF.getDirection() = run {
val d = this.getDistance()
require(d > 0f)
this / d
}
/**
* These epsilon values are used internally to determine when two points are the same, within
* some reasonable roundoff error. The distance epsilon is smaller, with the intention that the
* roundoff should not be larger than a pixel on any reasonable sized display.
*/
internal const val DistanceEpsilon = 1e-4f
internal const val AngleEpsilon = 1e-6f
internal fun PointF.rotate90() = PointF(-y, x)
internal val Zero = PointF(0f, 0f)
internal val FloatPi = Math.PI.toFloat()
internal fun Float.toRadians(): Float {
return this / 360f * TwoPi
}
internal val TwoPi: Float = 2 * Math.PI.toFloat()
internal fun directionVector(angleRadians: Float) = PointF(cos(angleRadians), sin(angleRadians))
internal fun square(x: Float) = x * x
internal fun PointF.copy(x: Float = Float.NaN, y: Float = Float.NaN) =
PointF(if (x.isNaN()) this.x else x, if (y.isNaN()) this.y else y)
internal fun PointF.angle() = ((atan2(y, x) + TwoPi) % TwoPi)
internal fun radialToCartesian(radius: Float, angleRadians: Float, center: PointF = Zero) =
directionVector(angleRadians) * radius + center
internal fun <T> Iterable<T>.sumOf(f: (T) -> Float) = map(f).sum()
internal fun positiveModule(num: Float, mod: Float) = (num % mod + mod) % mod
/*
* Does a ternary search in [v0..v1] to find the parameter that minimizes the given function.
* Stops when the search space size is reduced below the given tolerance.
*
* NTS: Does it make sense to split the function f in 2, one to generate a candidate, of a custom
* type T (i.e. (Float) -> T), and one to evaluate it ( (T) -> Float )?
*/
internal fun findMinimum(
v0: Float,
v1: Float,
tolerance: Float = 1e-3f,
f: (Float) -> Float
): Float {
var a = v0
var b = v1
while (b - a > tolerance) {
val c1 = (2 * a + b) / 3
val c2 = (2 * b + a) / 3
if (f(c1) < f(c2)) {
b = c2
} else {
a = c1
}
}
return (a + b) / 2
}
// Used to enable debug logging in the library
internal val DEBUG = true
internal inline fun debugLog(tag: String, messageFactory: () -> String) {
if (DEBUG) messageFactory().split("\n").forEach { Log.d(tag, it) }
}