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

commonMain.utils.Equations.kt Maven / Gradle / Ivy

The newest version!
package org.openrndr.kartifex.utils

import kotlin.math.abs
import kotlin.math.max
import kotlin.math.pow
import kotlin.math.sqrt

object Equations {
    private const val DISCRIMINANT_EPSILON = 1e-10
    private const val SOLUTION_EPSILON = 1e-8

    // adapted from https://github.com/paperjs/paper.js/blob/develop/src/util/Numerical.js
    private fun trim(acc: DoubleArray, len: Int): DoubleArray {
        return when (len) {
            acc.size -> {
                acc
            }
            0 -> {
                DoubleArray(0)
            }
            else -> {
                val result = DoubleArray(len)
                //java.lang.System.arraycopy(acc, 0, result, 0, len)
                acc.copyInto(result, 0, 0, len)
                result
            }
        }
    }

    private fun split(n: Double): DoubleArray {
        val x = n * 134217729
        val y = n - x
        val hi = y + x
        val lo = n - hi
        return doubleArrayOf(hi, lo)
    }

    private fun discriminant(a: Double, b: Double, c: Double): Double {
        var D = b * b - a * c
        val E = b * b + a * c
        if (abs(D) * 3 < E) {
            val ad = split(a)
            val bd = split(b)
            val cd = split(c)
            val p = b * b
            val dp = bd[0] * bd[0] - p + 2 * bd[0] * bd[1] + bd[1] * bd[1]
            val q = a * c
            val dq = ad[0] * cd[0] - q + ad[0] * cd[1] + ad[1] * cd[0] + ad[1] * cd[1]
            D = p - q + (dp - dq)
        }
        return D
    }

    fun solveLinear(a: Double, b: Double, acc: DoubleArray): Int {
        return if (abs(a) < Scalars.EPSILON) {
            0
        } else {
            acc[0] = -b / a
            1
        }
    }

    fun solveLinear(a: Double, b: Double): DoubleArray {
        val acc = DoubleArray(1)
        return trim(acc, solveLinear(a, b, acc))
    }

    @Suppress("NAME_SHADOWING")
    fun solveQuadratic(a: Double, b: Double, c: Double, acc: DoubleArray): Int {
        var a = a
        var b = b
        var c = c
        if (abs(a) < Scalars.EPSILON) {
            return solveLinear(b, c, acc)
        }
        b *= -0.5
        val k = Scalars.normalizationFactor(a, b, c)
        a *= k
        b *= k
        c *= k
        val D = discriminant(a, b, c)
        return if (D >= -DISCRIMINANT_EPSILON) {
            val Q: Double = if (D < 0) 0.0 else sqrt(D)
            val R = b + if (b < 0) -Q else Q
            if (R == 0.0) {
                acc[0] = c / a
                acc[1] = -c / a
            } else {
                acc[0] = R / a
                acc[1] = c / R
            }
            var writeIdx = 0
            for (readIdx in 0..1) {
                val x = acc[readIdx]

                // since the tolerance for the discriminant is fairly large, we check our work
                val y = a * x * x + -2 * b * x + c
                if (abs(y) < SOLUTION_EPSILON) {
                    acc[writeIdx++] = x
                }
            }
            writeIdx
        } else {
            0
        }
    }

    fun solveQuadratic(a: Double, b: Double, c: Double): DoubleArray {
        val acc = DoubleArray(2)
        return trim(acc, solveQuadratic(a, b, c, acc))
    }

    @Suppress("NAME_SHADOWING")
    fun solveCubic(a: Double, b: Double, c: Double, d: Double, acc: DoubleArray): Int {
        var a = a
        var b = b
        var c = c
        var d = d
        val k = Scalars.normalizationFactor(a, b, c, d)
        a *= k
        b *= k
        c *= k
        d *= k
        var x: Double
        var b1: Double
        var c2: Double
        var qd: Double
        var q: Double
        if (abs(a) < Scalars.EPSILON) {
            return solveQuadratic(b, c, d, acc)
        } else if (abs(d) < Scalars.EPSILON) {
            b1 = b
            c2 = c
            x = 0.0
        } else {
            x = -(b / a) / 3
            b1 = a * x + b
            c2 = b1 * x + c
            qd = (a * x + b1) * x + c2
            q = c2 * x + d
            val t = q / a
            val r: Double = abs(t).pow(1 / 3.0)
            val s: Double = if (t < 0) -1.0 else 1.0
            val td = -qd / a
            val rd = if (td > 0) 1.324717957244746 * max(r, sqrt(td)) else r
            var x0 = x - s * rd
            if (x0 != x) {
                do {
                    x = x0
                    b1 = a * x + b
                    c2 = b1 * x + c
                    qd = (a * x + b1) * x + c2
                    q = c2 * x + d
                    x0 = if (qd == 0.0) x else x - q / (qd / (1 + Scalars.MACHINE_EPSILON))
                } while (s * x0 > s * x)
                if (abs(a) * x * x > abs(d / x)) {
                    c2 = -d / x
                    b1 = (c2 - c) / x
                }
            }
        }
        var solutions = solveQuadratic(a, b1, c2, acc)
        for (i in 0 until solutions) {
            if (acc[i] == x) {
                return solutions
            }
        }
        val y = a * x * x * x + b * x * x + c * x + d
        if (abs(y) < SOLUTION_EPSILON) {
            acc[solutions++] = x
        }
        return solutions
    }

    fun solveCubic(a: Double, b: Double, c: Double, d: Double): DoubleArray {
        val acc = DoubleArray(3)
        return trim(acc, solveCubic(a, b, c, d, acc))
    }
}






© 2015 - 2025 Weber Informatics LLC | Privacy Policy