Scalar Functions (2D)

The Function2D interface represents a scalar function f(x) → Double and is the foundation for splines, parametric curves, and Bézier curves.

Function2D interface

interface Function2D {
    operator fun invoke(x: Double): Double
    operator fun invoke(xArray: DoubleArray): List<Double>
    operator fun invoke(xCollection: Collection<Double>): List<Double>
    fun derivative(x: Double): Double
    fun integrate(xStart: Double, xEnd: Double): Double
    fun tangentDirection(x: Double): Direction2D
    fun normalDirection(x: Double): Direction2D
}

Polynomial

A polynomial whose coefficients are ordered from the lowest degree to the highest:

Polynomial([a0, a1, a2, a3]) = a0 + a1·x + a2·x² + a3·x³
import plane.functions.Polynomial

// p(x) = 1 + 2x + 3x²
val p = Polynomial(listOf(1.0, 2.0, 3.0))

println(p(2.0))          // 1 + 4 + 12 = 17.0
println(p.order)         // 2

Derivative and integral

val p = Polynomial(listOf(1.0, 2.0, 3.0))   // 1 + 2x + 3x²

// Analytical derivative → 2 + 6x
val dp = p.derivative()
println(dp(1.0))   // 8.0

// Analytical indefinite integral (integration constant = 0)
val ip = p.integral()

// Definite integral between bounds
val area = p.integrate(0.0, 2.0)

Arithmetic between polynomials

val p1 = Polynomial(listOf(1.0, 2.0))   // 1 + 2x
val p2 = Polynomial(listOf(0.0, 1.0))   // x

val sum     = p1 + p2          // 1 + 3x
val product = p1 * p2          // x + 2x²
val squared = p1 pow 2         // (1 + 2x)²

Scalar arithmetic

val p = Polynomial(listOf(1.0, 2.0, 3.0))

val shifted  = p + 5.0    // adds 5 to the constant term
val scaled   = p * 2.0    // multiplies every coefficient by 2
val halved   = p / 2.0

LinearSpline

Piecewise linear interpolation through a set of Point2D nodes. x-values must be strictly ascending.

import plane.functions.LinearSpline
import plane.elements.Point2D

val spline = LinearSpline(listOf(
    Point2D(0.0, 0.0),
    Point2D(1.0, 2.0),
    Point2D(2.0, 1.0),
    Point2D(3.0, 3.0)
))

println(spline(0.5))              // 1.0  (midpoint of first segment)
println(spline.derivative(0.5))   // 2.0  (slope of first segment)
println(spline.integrate(0.0, 3.0))

Note

The derivative is constant within each segment and undefined at knots (returns the slope of the left segment).


CubicSpline

A natural cubic spline (second derivative = 0 at both endpoints) fitted through a set of Point2D nodes. Requires at least 4 points with strictly ascending x-values.

Each segment between two consecutive knots is represented as a Polynomial of degree ≤ 3.

import plane.functions.CubicSpline
import plane.elements.Point2D

val spline = CubicSpline(listOf(
    Point2D(0.0, 0.0),
    Point2D(1.0, 2.0),
    Point2D(2.0, 1.5),
    Point2D(3.0, 3.0),
    Point2D(4.0, 1.0)
))

val y     = spline(1.5)               // interpolated value
val slope = spline.derivative(1.5)    // smooth first derivative
val area  = spline.integrate(0.0, 4.0)

Accessing the underlying polynomials

spline.polynomials.forEachIndexed { i, poly ->
    println("Segment $i: coefficients = ${poly.coefficients}")
}

Visualization

// requires geomez-visualization
spline.plot()

The blue dashed line shows a LinearSpline and the red line a CubicSpline through the same knot points (black dots):

Spline comparison


Tangent and normal directions

All Function2D implementations expose:

val spline = CubicSpline(points)

// Unit tangent direction at x = 1.5
val tangent = spline.tangentDirection(1.5)   // Direction2D

// Unit normal direction (perpendicular, anti-clockwise)
val normal  = spline.normalDirection(1.5)    // Direction2D