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

commonMain.Curve2.kt Maven / Gradle / Ivy

The newest version!
package org.openrndr.kartifex

import org.openrndr.kartifex.utils.Intersections

interface Curve2 {
    /**
     * @param t a parametric point on the curve, not necessarily within [0, 1]
     * @return the interpolated position on the curve
     */
    fun position(t: Double): Vec2

    /**
     * Given a ring of curves, the sum of area() will be the area enclosed by that ring. For clockwise rings, the sum will
     * be negative, for counter-clockwise rings it will be positive.
     *
     * @return the signed area of the curve
     */
    fun signedArea(): Double
    fun length(): Double
    fun start() = position(0.0)

    fun end() = position(1.0)

    /**
     * @return an updated curve with the specified endpoints.
     */
    fun endpoints(start: Vec2, end: Vec2): Curve2

    /**
     * @param t a parametric point on the curve, not necessarily within [0, 1]
     * @return the tangent at the interpolated position on the curve, which is not normalized
     */
    fun direction(t: Double): Vec2

    /**
     * @param t a parametric point within the curve, which must be within (0, 1)
     * @return an array representing the lower and upper regions of the curve, split at `t`
     */
    fun split(t: Double): Array
    fun isFlat(epsilon: Double): Boolean

    /**
     * @param interval the parametric range
     * @return the curve within [interval.lo, interval.hi]
     */
    fun range(interval: Interval) = range(interval.lo, interval.hi)

    /**
     * @param tMin the lower parametric bound
     * @param tMax the upper parametric bound
     * @return the curve within [tMin, tMax]
     */
    fun range(tMin: Double, tMax: Double): Curve2 {
        require(tMin != tMax) { "range must be non-zero" }
        require(tMax >= tMin) { "tMin must be less than tMax" }

        return when {
            tMin == 0.0 && tMax == 1.0 -> this
            tMin == 0.0 -> split(tMax)[0]
            tMax == 1.0 -> split(tMin)[1]
            else -> split(tMin)[1].split((tMax - tMin) / (1 - tMin))[0].endpoints(position(tMin), position(tMax))
        }
    }

    /**
     * @param unsafeTs an array of parametric split points
     * @return an array of curves, split at the specified points.
     */
    fun split(unsafeTs: DoubleArray): Array {
        var previous = Double.POSITIVE_INFINITY
        var tCount = 0
        for (i in unsafeTs.indices) {
            if (unsafeTs[i] != previous) {
                tCount++
            }
            previous = unsafeTs[i]
        }
        var ts = DoubleArray(tCount)
        tCount = 0
        previous = Double.POSITIVE_INFINITY
        for (i in unsafeTs.indices) {
            if (unsafeTs[i] != previous) {
                ts[tCount] = unsafeTs[i]
                tCount++
            }
            previous = unsafeTs[i]
        }
        if (ts.isEmpty()) {
            return arrayOf(this)
        }
        //ts = ts.clone()
        //java.util.Arrays.sort(ts)

        ts = ts.map { it }.toDoubleArray()
        ts.sort()

        val offset = if (ts[0] == 0.0) 1 else 0
        val len = ts.size - offset - if (ts[ts.size - 1] == 1.0) 1 else 0

        ts.copyInto(ts, 0, offset, offset+len)


        if (len == 0) {
            return arrayOf(this)
        } else if (len == 1) {
            return split(ts[0])
        }
        val result = arrayOfNulls(len + 1)
        result[0] = range(0.0, ts[0])
        for (i in 0 until len - 1) {
            result[i + 1] = range(ts[i], ts[i + 1])
        }
        result[len] = range(ts[len - 1], 1.0)
        return result.filterNotNull().toTypedArray()
    }

    /**
     * @param p a point in 2D space
     * @return the `t` parameter representing the closest point on the curve, not necessarily within [0,1]
     */
    fun nearestPoint(p: Vec2): Double
    fun bounds(): Box2 {
        var bounds: Box2 = Box.box(start(), end())
        for (t in inflections()) {
            bounds = bounds.union(position(t))
        }
        return bounds
    }

    fun subdivide(error: Double): Array
    fun transform(m: Matrix3): Curve2
    fun reverse(): Curve2
    fun inflections(): DoubleArray

    fun intersections(c: Curve2) = Intersections.intersections(this, c)
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy