commonMain.ru.casperix.math.angle.float64.RadianDouble.kt Maven / Gradle / Ivy
package ru.casperix.math.angle.float64
import ru.casperix.math.angle.Angle
import ru.casperix.math.angle.AngleBuilder
import ru.casperix.math.angle.float32.RadianFloat
import ru.casperix.math.geometry.RADIAN_TO_DEGREE
import ru.casperix.math.geometry.fPI
import ru.casperix.math.geometry.fPI2
import ru.casperix.math.interpolation.float64.InterpolateDoubleFunction
import ru.casperix.math.interpolation.float64.linearInterpolate
import ru.casperix.math.vector.float64.Vector2d
import ru.casperix.misc.toPrecision
import kotlinx.serialization.Serializable
import kotlin.jvm.JvmInline
import kotlin.math.absoluteValue
import kotlin.math.atan2
import kotlin.math.cos
import kotlin.math.sin
@JvmInline
@Serializable
value class RadianDouble(val value: Double) : ru.casperix.math.angle.Angle {
/**
* @return angle in [0, 2PI) interval
*/
override fun normalize(): RadianDouble {
return RadianDouble(absMod(value, PI2.value))
}
override fun isFinite(): Boolean {
return value.isFinite()
}
override operator fun plus(other: RadianDouble): RadianDouble {
return RadianDouble(value + other.value)
}
override operator fun minus(other: RadianDouble): RadianDouble {
return RadianDouble(value - other.value)
}
override operator fun plus(other: Double): RadianDouble {
return RadianDouble(value + other)
}
override operator fun minus(other: Double): RadianDouble {
return RadianDouble(value - other)
}
override operator fun unaryMinus(): RadianDouble {
return RadianDouble(-value)
}
override operator fun times(factor: Double): RadianDouble {
return RadianDouble(value * factor)
}
override operator fun div(factor: Double): RadianDouble {
return RadianDouble(value / factor)
}
override operator fun compareTo(other: Double): Int {
return value.compareTo(other)
}
override operator fun compareTo(other: RadianDouble): Int {
return value.compareTo(other.value)
}
fun toDegree(): DegreeDouble {
return DegreeDouble(value * RADIAN_TO_DEGREE)
}
fun toRadianFloat(): RadianFloat {
return RadianFloat(value.toFloat())
}
override fun format(precision: Int): String {
return value.toPrecision(precision)
}
override fun toString(): String {
return format(3)
}
fun toDirection(): Vector2d {
return Vector2d(cos(value), sin(value))
}
fun distTo(other: RadianDouble): RadianDouble {
return betweenAngle(this, other)
}
companion object : AngleBuilder {
override val ZERO = RadianDouble(0.0)
val PI2 = RadianDouble(ru.casperix.math.geometry.PI2)
val PI = RadianDouble(kotlin.math.PI)
val HPI = RadianDouble(ru.casperix.math.geometry.HPI)
override val MAX = PI2
/**
* @see [byDirectionRadian]
*/
override fun byDirection(value: Vector2d): RadianDouble {
return byDirection(value.x, value.y)
}
/**
* @see [byDirectionRadian]
*/
override fun byDirection(x: Double, y: Double): RadianDouble {
return RadianDouble(byDirectionRadian(x, y))
}
/**
* @return angle from X-axis to vector in [0, 2PI) interval
*/
fun byDirectionRadian(x: Double, y: Double): Double {
val result = atan2(y, x)
return if (result < 0f) PI2.value + result
else result
// val xAbs = x.absoluteValue
// val yAbs = y.absoluteValue
// return (PI.value - HPI.value * (1.0 + x.sign) * (1.0 - (y * y).sign) - HPI.value * 0.5 * (2.0 + x.sign) * y.sign - (x * y).sign * atan((xAbs - yAbs) / (xAbs + yAbs)))
}
private fun absMod(value: Double, mod: Double): Double {
return if (value < 0f) {
(mod - (-value) % mod) % mod
} else {
value % mod
}
}
/**
* @return angle in [0, 2PI) interval
*/
fun interpolateAngular(
start: RadianDouble,
finish: RadianDouble,
position: Double,
interpolator: InterpolateDoubleFunction = linearInterpolate
): RadianDouble {
val startNormalized = start.normalize().value
val finishNormalized = finish.normalize().value
val finishAdapted = if ((startNormalized - finishNormalized).absoluteValue <= fPI) {
finishNormalized
} else if (startNormalized > finishNormalized) {
finishNormalized + fPI2
} else {
finishNormalized - fPI2
}
return RadianDouble(interpolator(startNormalized, finishAdapted, position)).normalize()
}
fun betweenAngle(a: RadianDouble, b: RadianDouble): RadianDouble {
val startNormalized = a.normalize().value
val finishNormalized = b.normalize().value
val finishAdapted = if ((startNormalized - finishNormalized).absoluteValue <= fPI) {
finishNormalized
} else if (startNormalized > finishNormalized) {
finishNormalized + fPI2
} else {
finishNormalized - fPI2
}
return RadianDouble((startNormalized - finishAdapted).absoluteValue).normalize()
}
}
}