![JAR search and dependency download from the Maven repository](/logo.png)
commonMain.ru.casperix.math.curve.float32.Arc2f.kt Maven / Gradle / Ivy
package ru.casperix.math.curve.float32
import ru.casperix.math.geometry.float32.Geometry2Float
import ru.casperix.math.polar.float32.PolarCoordinateFloat
import ru.casperix.math.angle.float32.RadianFloat
import ru.casperix.math.geometry.*
import ru.casperix.math.geometry.float32.toLine2d
import ru.casperix.math.interpolation.float32.linearInterpolatef
import ru.casperix.math.intersection.float64.Intersection2Double
import ru.casperix.math.vector.float32.Vector2f
import ru.casperix.math.vector.rotateCCW
import ru.casperix.math.vector.rotateCW
import kotlinx.serialization.Serializable
import kotlin.math.absoluteValue
import kotlin.math.sign
/**
* @param center -- center of circle on which arc is placed
* @param startAngle -- angle in radian for start point
* @param deltaAngle -- delta by circle to end point (positive -- counterclockwise, or negative -- clockwise)
*/
@Serializable
data class Arc2f(val center: Vector2f, val startAngle: RadianFloat, val deltaAngle: RadianFloat, val range: Float) :
ParametricCurve2f {
val finishAngle = startAngle + deltaAngle
override val start: Vector2f = center + PolarCoordinateFloat(range, startAngle).toDecart()
override val finish: Vector2f = center + PolarCoordinateFloat(range, finishAngle).toDecart()
override fun invert(): Arc2f {
return Arc2f(center, finishAngle, -deltaAngle, range)
}
fun isAngleInside(angle: RadianFloat): Boolean {
var control = (angle - startAngle).normalize().value
val deltaAngle = deltaAngle.value
return if (deltaAngle >= 0f) {
if (control < 0f) control += fPI2
control in 0f..deltaAngle
} else {
if (control > 0f) control -= fPI2
control in deltaAngle..0f
}
}
override fun grow(startOffset: Float, finishOffset: Float): ParametricCurve2f {
val changeStartAngle = startOffset / range * deltaAngle.value.sign
val changeFinishAngle = finishOffset / range * deltaAngle.value.sign
return Arc2f(center, startAngle - changeStartAngle, deltaAngle + (changeStartAngle + changeFinishAngle), range)
}
override fun getProjection(position: Vector2f): Float {
val angle = RadianFloat.byDirection(position - center)
var control = (angle - startAngle).normalize().value
val deltaAngle = deltaAngle.value
return if (deltaAngle >= 0f) {
if (control < 0f) control += fPI2
control / deltaAngle
} else {
if (control > 0f) control -= fPI2
control / deltaAngle
}
}
companion object {
fun byRadian(center: Vector2f, startAngle: Float, deltaAngle: Float, range: Float): Arc2f {
return Arc2f(center, RadianFloat(startAngle), RadianFloat(deltaAngle), range)
}
/**
* @param start - first arc point.
* @param startTangent - tangent for first arc point
* @param finish - last arc point
*/
fun byTangent(start: Vector2f, startTangent: Vector2f, finish: Vector2f): ParametricCurve2f {
val startNormal = startTangent.rotateCCW()
val basis = (finish - start)
val middleNormal = basis.rotateCW()
val middlePoint = (finish + start) / 2f
// The function is sensitive to accuracy
val center = Intersection2Double.getLineWithLine(
Line2f.byDelta(start, startNormal).toLine2d(),
Line2f.byDelta(middlePoint, middleNormal).toLine2d()
)?.toVector2f() ?: return LineCurve2f(start, finish)//throw Exception("Invalid center")
val startAngle = RadianFloat.byDirection(start - center).value
var finishAngle = RadianFloat.byDirection(finish - center).value
val range = center.distTo(start)
val pad = Geometry2Float.getPointAroundRay(Vector2f.ZERO, startTangent, finish - start, 0f)
if (pad == PointAroundRay.LEFT) {
if (finishAngle < startAngle) {
finishAngle += fPI2
}
} else {
if (finishAngle > startAngle) {
finishAngle -= fPI2
}
}
val deltaAngle = finishAngle - startAngle
return Arc2f(center, RadianFloat(startAngle), RadianFloat(deltaAngle), range)
}
}
override fun divide(t: Float): Pair {
val deltaAngleFirst = linearInterpolatef(0f, deltaAngle.value, t)
val deltaAngleLast = deltaAngle.value - deltaAngleFirst
return Pair(
Arc2f(center, startAngle, RadianFloat(deltaAngleFirst), range),
Arc2f(center, startAngle + RadianFloat(deltaAngleFirst), RadianFloat(deltaAngleLast), range),
)
}
fun getAngle(t: Float): RadianFloat {
return RadianFloat(linearInterpolatef(startAngle.value, finishAngle.value, t))
}
override fun getPosition(t: Float): Vector2f {
val angle = getAngle(t)
return center + PolarCoordinateFloat(range, angle).toDecart()
}
override fun getTangent(t: Float): Vector2f {
val delta = if (deltaAngle > 0f) fPI / 2f else -fPI / 2f
return PolarCoordinateFloat(1f, getAngle(t) + delta).toDecart()
}
override fun getNormal(t: Float): Vector2f {
val delta = RadianFloat(if (deltaAngle.value > 0f) -fPI else 0f)
return PolarCoordinateFloat(1f, getAngle(t) + delta).toDecart()
}
override fun length(): Float {
return range * deltaAngle.value.absoluteValue
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy