All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.ru.casperix.math.curve.float32.Arc2f.kt Maven / Gradle / Ivy

There is a newer version: 1.9.0
Show newest version
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 - 2024 Weber Informatics LLC | Privacy Policy