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

commonMain.ru.casperix.math.curve.float64.Arc2d.kt Maven / Gradle / Ivy

package ru.casperix.math.curve.float64

import ru.casperix.math.geometry.float64.Geometry2Double
import ru.casperix.math.polar.float64.PolarCoordinateDouble
import ru.casperix.math.angle.float64.RadianDouble
import ru.casperix.math.geometry.*
import ru.casperix.math.interpolation.float64.linearInterpolate
import ru.casperix.math.intersection.float64.Intersection2Double
import ru.casperix.math.vector.float64.Vector2d
import ru.casperix.math.vector.rotateCCW
import ru.casperix.math.vector.rotateCW
import kotlinx.serialization.Serializable
import kotlin.math.PI
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 Arc2d(val center: Vector2d, val startAngle: RadianDouble, val deltaAngle: RadianDouble, val range: Double) : Curve2d {
    val finishAngle = startAngle + deltaAngle

    override val start: Vector2d = center + PolarCoordinateDouble(range, startAngle).toDecart()
    override val finish: Vector2d = center + PolarCoordinateDouble(range, finishAngle).toDecart()

    override fun invert(): Arc2d {
        return Arc2d(center, finishAngle, -deltaAngle, range)
    }

    fun isAngleInside(angle: RadianDouble): Boolean {
        var control = (angle - startAngle).normalize().value
        val deltaAngle = deltaAngle.value

        return if (deltaAngle >= 0f) {
            if (control < 0f) control += PI2
            control in 0.0..deltaAngle
        } else {
            if (control > 0f) control -= PI2
            control in deltaAngle..0.0
        }
    }

    override fun grow(startOffset: Double, finishOffset: Double): Curve2d {
        val changeStartAngle = RadianDouble(startOffset / range * deltaAngle.value.sign)
        val changeFinishAngle = RadianDouble(finishOffset / range * deltaAngle.value.sign)
        return Arc2d(center, startAngle - changeStartAngle, deltaAngle + (changeStartAngle + changeFinishAngle), range)
    }

    override fun getProjection(position: Vector2d): Double {
        val angle = RadianDouble.byDirection(position - center)
        var control = (angle - startAngle).normalize().value
        val deltaAngle = deltaAngle.value

        return if (deltaAngle >= 0) {
            if (control < 0f) control += fPI2
            control /deltaAngle
        } else {
            if (control > 0f) control -= fPI2
            control / deltaAngle
        }
    }

    companion object {
        /**
         * @param start - first arc point.
         * @param startTangent - tangent for first arc point
         * @param finish - last arc point
         */
        fun byTangent(start: Vector2d, startTangent: Vector2d, finish: Vector2d): Arc2d {
            val startNormal = startTangent.rotateCCW()
            val basis = (finish - start)
            val middleNormal = basis.rotateCW()
            val middlePoint = (finish - start) / 2.0

            val centerOffset = Intersection2Double.getLineWithLine(
                Line2d(Vector2d.ZERO, startNormal),
                Line2d(middlePoint, middlePoint+middleNormal)
            ) ?: throw Exception("Invalid center")

            val center = start + centerOffset

            val startAngle = RadianDouble.byDirection(start - center).value
            var finishAngle = RadianDouble.byDirection(finish - center).value

            val range = center.distTo(start)
            val pad = Geometry2Double.getPointAroundRay(Vector2d.ZERO, startTangent, finish - start, 0.0)
            if (pad == PointAroundRay.LEFT) {
                if (finishAngle < startAngle) {
                    finishAngle += fPI2
                }
            } else {
                if (finishAngle > startAngle) {
                    finishAngle -= fPI2
                }
            }
            val deltaAngle = finishAngle - startAngle
            return Arc2d(center, RadianDouble(startAngle), RadianDouble(deltaAngle), range)
        }
    }

    override fun divide(t: Double): Pair {
        val deltaAngleFirst = linearInterpolate(0.0, deltaAngle.value, t)
        val deltaAngleLast = deltaAngle.value - deltaAngleFirst

        return Pair(
            Arc2d(center, startAngle, RadianDouble(deltaAngleFirst), range),
            Arc2d(center, startAngle + RadianDouble(deltaAngleFirst), RadianDouble(deltaAngleLast), range),
        )
    }

    fun getAngle(t: Double): RadianDouble {
        return RadianDouble(linearInterpolate(startAngle.value, finishAngle.value, t))
    }

    override fun getPosition(t: Double): Vector2d {
        val angle = getAngle(t)
        return center + PolarCoordinateDouble(range, angle).toDecart()
    }

    override fun getTangent(t: Double): Vector2d {
        val delta = if (deltaAngle.value > 0.0) PI / 2 else -PI / 2
        return PolarCoordinateDouble(1.0, getAngle(t) + RadianDouble(delta)).toDecart()
    }

    override fun getNormal(t: Double): Vector2d {
        val delta = if (deltaAngle.value > 0.0) -PI else 0.0
        return PolarCoordinateDouble(1.0, getAngle(t) + RadianDouble(delta)).toDecart()
    }

    override fun length(): Double {
        return range * deltaAngle.value.absoluteValue
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy