commonMain.ru.casperix.math.curve.float32.Arc2f.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of math Show documentation
Show all versions of math Show documentation
Simple set of geometric and other types
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
}
}