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

commonMain.korlibs.math.geom.Ellipse.kt Maven / Gradle / Ivy

package korlibs.math.geom

import korlibs.math.geom.shape.*
import korlibs.math.geom.vector.*
import kotlin.math.*

data class Ellipse(override val center: Point, val radius: Size) : Shape2D {
    override val area: Float get() = (PI * radius.widthD * radius.heightD).toFloat()
    override val perimeter: Float get() {
        if (radius.width == radius.height) return (2 * PI * radius.width).toFloat() // Circle formula
        val (a, b) = radius
        val h = ((a - b) * (a - b)) / ((a + b) * (a + b))
        return (PI * (a + b) * (1 + ((3 * h) / (10 + sqrt(4 - (3 * h)))))).toFloat()
    }

    override fun distance(p: Point): Float {
        val p = p - center
        val scaledPoint = Vector2(p.x / radius.width, p.y / radius.height)
        val length = scaledPoint.length
        return (length - 1) * min(radius.width, radius.height)
    }

    override fun normalVectorAt(p: Point): Vector2 {
        val pointOnEllipse = p - center
        val (a, b) = radius
        val normal = Vector2(pointOnEllipse.x / (a * a), pointOnEllipse.y / (b * b))
        return normal.normalized
        //val d = p - center
        //val r2 = radius.toVector() * radius.toVector()
        //return (d / r2).normalized
    }

    override fun projectedPoint(p: Point): Point {
        val angle = Angle.between(center, p)
        return center + Point(radius.width * angle.cosineF, radius.height * angle.sineF)

        //val k = (radius.width * radius.height) / sqrt()
        //return projectPointOntoEllipse(p, center, radius.toVector())
    }

    override fun containsPoint(p: Point): Boolean {
        if (radius.isEmpty()) return false
        // Check if the point is inside the ellipse using the ellipse equation:
        // (x - centerX)^2 / radiusX^2 + (y - centerY)^2 / radiusY^2 <= 1
        return ((p.x - center.x).pow(2f) / radius.width.pow(2)) + ((p.y - center.y).pow(2) / radius.height.pow(2)) <= 1
    }

    override fun toVectorPath(): VectorPath = buildVectorPath { ellipse([email protected], [email protected]) }

    companion object {
        private fun projectPointOntoEllipse(point: Vector2, center: Vector2, radius: Vector2, tolerance: Double = 1e-6, maxIterations: Int = 100): Vector2 {
            var currentPoint = point
            var i = 0

            while (i < maxIterations) {
                val dx = currentPoint.x - center.x
                val dy = currentPoint.y - center.y
                val rx2 = radius.x * radius.x
                val ry2 = radius.y * radius.y

                val f = Vector2(
                    (dx * rx2 - dy * dx * dy) / (rx2 * ry2),
                    (dy * ry2 - dx * dy * dx) / (rx2 * ry2)
                )

                val df = Vector2(
                    (ry2 - 2.0 * dy * dy) / (rx2 * ry2),
                    (rx2 - 2.0 * dx * dx) / (rx2 * ry2)
                )

                val nextPoint = currentPoint - f / df
                val dist = (nextPoint - currentPoint).length

                if (dist < tolerance) return nextPoint

                currentPoint = nextPoint
                i++
            }

            return currentPoint
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy