Last active
March 11, 2025 12:07
-
-
Save ingoogni/490d2d50710bab4fe5e689714e9ef287 to your computer and use it in GitHub Desktop.
Spline and curve interpolation for many variants. Based upon the works of Steve H. Noskowicz. Parallel excecution using Malebolgia.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import sequtils, math | |
## Spline and curve interpolation for many variants. | |
## Based upon the works of Steve H. Noskowicz. | |
## http://web.archive.org/web/20151002232205/http://home.comcast.net/~k9dci/site/?/page/Piecewise_Polynomial_Interpolation/ | |
## Get the book PDF and the appendix. | |
type | |
CurveSegmentError* = object of Defect | |
CurveOrderError* = object of Defect | |
CurveParameterError* = object of Defect | |
Curve*[T] = object | |
segmentDim*: int # number of control points per segment | |
stepSize*: int # number of control points to get to the next | |
# segment. | |
segments*: int # number of segments in curve, number of control | |
# points in a multi segment curve is | |
# `segmentDim + (n * stepSize)` | |
matrix*: seq[seq[float]] # base matrix | |
degree*: int # degree of the curve | |
cp*: seq[T] # sequence of control points | |
curmult*: seq[seq[T]] # control points multiplied by weights | |
origin*: T # ... to get the type from ... | |
ICurve*[T] = object | |
stepSize*: int | |
segmentDim*: int | |
matrix*: seq[seq[float]] | |
degree*: int | |
cp*: iterator (): T | |
origin*: T | |
#if three dimensions (vmath) is not enough | |
func `+`[T:float](a, b: openArray[T]): seq[T] = | |
result = newSeq[T](a.len) | |
for i,j in a: | |
result[i] = j + b[i] | |
func `*`[T:float](a, b: seq[T]):seq[T]= | |
result = newSeq[T](a.len) | |
for i,j in a: | |
result[i] = j * b[i] | |
func nSegments(length: int, stepSize: int, segmentDim: int): int {.inline.} = | |
let lsd = length - segmentDim | |
if lsd < 0: | |
raise newException(CurveSegmentError, "Too few points in curve array") | |
if (lsd mod stepSize) == 0: | |
result = 1 + (lsd div stepSize) | |
else: | |
raise newException(CurveSegmentError, "Incorrect number of points in curve array") | |
func precalcCurmult(curve: var Curve) = | |
curve.curmult = newSeq[seq[curve.origin.type]](curve.segments) | |
for segment in 0 ..< curve.segments: | |
let current_pos = segment * curve.stepSize | |
curve.curmult[segment] = newSeq[curve.origin.type](len(curve.matrix)) | |
# Calculate each coefficient | |
for i in 0 ..< len(curve.matrix): | |
curve.curmult[segment][i] = default(curve.origin.type) | |
# Accumulate the weighted sum | |
for j in 0 ..< len(curve.matrix[i]): | |
let jSegment = j + current_pos | |
curve.curmult[segment][i] += curve.cp[jSegment] * curve.matrix[i][j] | |
func divideMatrix(matrix: seq[seq[float]], divisor: float): seq[seq[float]] {.inline.} = | |
matrix.mapIt(it.mapIt(it / divisor)) | |
func initCurve*[T]( | |
cp: seq[T] or iterator (): T, # control points | |
segmentDim: int, | |
stepSize: int, | |
degree: int, | |
matrix: seq[seq[float]], | |
): Curve[T] | ICurve[T] = | |
when cp is iterator (): T: | |
var curve = ICurve[T]() | |
else: | |
var curve = Curve[T]() | |
curve.cp = cp | |
curve.segmentDim = segmentDim | |
curve.stepSize = stepSize | |
curve.degree = degree | |
curve.matrix = matrix | |
when curve is Curve[T]: | |
curve.segments = nSegments(len(curve.cp), curve.stepSize, curve.segmentDim) | |
precalcCurmult(curve) | |
return curve | |
func linear*[T](cp: seq[T] or iterator (): T): Curve[T] | ICurve[T] = | |
## Linear "curve". | |
## Two control points define a segment. | |
## The curve passes through all control points. | |
## Number of control points in a multi segment curve is `2 + (n * 1)` | |
initCurve( | |
cp = cp, | |
segmentDim = 2, | |
stepSize = 1, | |
degree = 1, | |
matrix = @[ | |
@[-1.0, 1.0], | |
@[1.0] | |
] | |
) | |
func simple3*[T](cp: seq[T] or iterator (): T): Curve[T] | ICurve[T] = | |
## Simple Cubic curve. Linear segments | |
## Two control points define a segment. | |
## The curve passes through all control points. | |
## Number of control points in a multi segment curve is `2 + (n * 1)` | |
initCurve( | |
cp = cp, | |
segmentDim = 2, | |
stepSize = 1, | |
degree = 3, | |
matrix = @[ | |
@[2.0, -2.0], | |
@[-3.0, 3.0], | |
@[0.0, 0.0], | |
@[1.0] | |
] | |
) | |
func simple3ESC*[T](cp: seq[T] or iterator (): T, esc: float): Curve[T] | ICurve[T] = | |
## Simple Cubic curve with Endpoint Slope Control. Linear segments | |
## Two control points define a segment. | |
## The curve passes through all control points. | |
## Number of control points in a multi segment curve is `2 + (n * 1)` | |
## `esc`: controls the velocity, spacing of points. It goes to the value of | |
## `esc` at the control points. | |
initCurve( | |
cp = cp, | |
segmentDim = 2, | |
stepSize = 1, | |
degree = 3, | |
matrix = @[ | |
@[2.0 - (2.0 * esc), (2.0 * esc) - 2.0], | |
@[(3.0 * esc) - 3.0, 3.0 - (3.0 * esc)], | |
@[-esc, esc], | |
@[1.0], | |
], | |
) | |
func simple3MSC*[T](cp: seq[T] or iterator (): T, msc: float): Curve[T] | ICurve[T] = | |
## Simple Cubic curve with Midpoint Slope Control | |
## Two control points define a segment. | |
## The curve passes through all control points. | |
## Number of control points in a multi segment curve is `2 + (n * 1)` | |
## `msc`: controls the velocity, spacing of points. It goes towards the value | |
## of `msc` at the mid point between the control points. | |
initCurve( | |
cp = cp, | |
segmentDim = 2, | |
stepSize = 1, | |
degree = 3, | |
matrix = @[ | |
@[(4.0 * msc) - 4.0, 4.0 - (4.0 * msc)], | |
@[6.0 - (6.0 * msc), (6.0 * msc) - 6.0], | |
@[(2.0 * msc) - 3.0, 3.0 - (2.0 * msc)], | |
@[1.0], | |
], | |
) | |
func simple3IESC*[T](cp: seq[T] or iterator (): T, esc0: float, esc1: float): Curve[T] | ICurve[T] = | |
## Simple Cubic with Independent Endpoint Slope Control | |
## Two control points define a segment. | |
## The curve passes through all control points. | |
## Number of control points in a multi segment curve is `2 + (n * 1)` | |
## `esc0`, `esc1`: control the velocity, spacing of points. It goes to the | |
## value of `esc` at the control point. | |
initCurve( | |
cp = cp, | |
segmentDim = 2, | |
stepSize = 1, | |
degree = 3, | |
matrix = @[ | |
@[2.0 - esc0 - esc1, esc0 + esc1 - 2.0], | |
@[(2.0 * esc0) + esc1 - 3.0, 3.0 - (2.0 * esc0) - esc1], | |
@[-esc0, esc0], | |
@[1.0], | |
], | |
) | |
func bezier2*[T](cp: seq[T] or iterator (): T): Curve[T] | ICurve[T] = | |
## Quadratic Bezier curve | |
## Three control points define a segment. | |
## The curve passes through the first and last control point of a segment. | |
## The curve is "attracted" to the middel control point. | |
## Number of control points in a multi segment curve is `3 + (n * 2)` | |
initCurve( | |
cp = cp, | |
segmentDim = 3, | |
stepSize = 2, | |
degree = 2, | |
matrix = @[ | |
@[1.0, -2.0, 1.0], | |
@[-2.0, 2.0], | |
@[1.0] | |
] | |
) | |
func bezier2EVC*[T](cp: seq[T] or iterator (): T, evc: float): Curve[T] | ICurve[T] = | |
## Quadratic Bezier curve with Endpoint Velocity Control | |
## Three control points define a segment. | |
## The curve passes through the first and last control point of a segment. | |
## The curve is "attracted" to the middel control point. | |
## Number of control points in a multi segment curve is `3 + (n * 2)` | |
## `evc`: Changes the length of the end tangent vectors. This pushes the | |
## centre of the curve towards the middle control point. At `evc = 0` it | |
## is a quadratic Bezier curve. At `evc = -2` it is a simple cubic curve | |
## and at `evc = 2` the curve goes through the middle control point. | |
initCurve( | |
cp = cp, | |
segmentDim = 3, | |
stepSize = 2, | |
degree = 2, | |
matrix = @[ | |
@[-evc, 0.0, evc], | |
@[1.0 + (2.0 * evc), -2.0 - evc, 1.0 - evc], | |
@[-2.0 - evc, 2.0 + evc], | |
@[1.0], | |
] | |
) | |
func bezier3*[T](cp: seq[T] or iterator (): T): Curve[T] | ICurve[T] = | |
## Cubic Bezier curve | |
## Four control points define a segment. | |
## The curve passes through the first and last control point of a segment. | |
## The curve is "attracted" to the middel control points. | |
## Number of control points in a multi segment curve is `4 + (n * 3)` | |
initCurve( | |
cp = cp, | |
segmentDim = 4, | |
stepSize = 3, | |
degree = 3, | |
matrix = @[ | |
@[-1.0, 3.0, -3.0, 1.0], | |
@[3.0, -6.0, 3.0], | |
@[-3.0, 3.0], | |
@[1.0] | |
] | |
) | |
func bezier3EVC*[T](cp: seq[T] or iterator (): T, evc: float): Curve[T] | ICurve[T] = | |
## Cubic Bezier Curve with Endpoint Velocity Control | |
## Four control points define a segment. | |
## The curve passes through the first and last control point of a segment. | |
## The curve is "attracted" to the middel control points. | |
## Number of control points in a multi segment curve is `4 + (n * 3)` | |
## `evc`: Changes the length of the end tangent vectors. This pushes | |
## the curve towards the middle control point. | |
initCurve( | |
cp = cp, | |
segmentDim = 4, | |
stepSize = 3, | |
degree = 3, | |
matrix = @[ | |
@[-1.0 - evc, 3.0 + evc, -3.0 - evc, 1.0 + evc], | |
@[3.0 + (2 * evc), -6.0 - (2 * evc), 3.0 + evc, -evc], | |
@[-3.0 - evc, 3.0 + evc], | |
@[1.0] | |
] | |
) | |
func bspline2*[T](cp: seq[T] or iterator (): T): Curve[T] | ICurve[T] = | |
## Quadratic b-spline aka parabolic spline | |
## Three control points define a segment. | |
## The curve does not pass through any control point. It starts half way P0P1 | |
## and ends half way P1P2. | |
## The curve is "attracted" to the middel control points. | |
## Number of control points in a multi segment curve is `3 + (n * 1)` | |
initCurve( | |
cp = cp, | |
segmentDim = 3, | |
stepSize = 1, | |
degree = 2, | |
matrix = divideMatrix( | |
@[ | |
@[1.0, -2.0, 1.0], | |
@[-2.0, 2.0], | |
@[1.0, 1.0] | |
], | |
2.0 | |
) | |
) | |
func bspline3*[T](cp: seq[T] or iterator (): T): Curve[T] | ICurve[T] = | |
## Cubic b-spline | |
## Four control points define a segment. | |
## The curve does not pass through any control point. It goes from near | |
## P1 to near P2 | |
## The curve is "attracted" to the middel control points. | |
## Number of control points in a multi segment curve is `4 + (n * 1)` | |
initCurve( | |
cp = cp, | |
segmentDim = 4, | |
stepSize = 1, | |
degree = 3, | |
matrix = divideMatrix( | |
@[ | |
@[-1.0, 3.0, -3.0, 1.0], | |
@[3.0, -6.0, 3.0], | |
@[-3.0, 0.0, 3.0], | |
@[1.0, 4.0, 1.0] | |
], | |
6.0 | |
), | |
) | |
func cardinal*[T](cp: seq[T] or iterator (): T, tension: float): Curve[T] | ICurve[T] = | |
## Cardinal spline | |
## Four control points define a segment. | |
## Number of control points in a multi segment curve is `4 + (n * 1)` | |
let t = (1.0 - tension) / 2.0 | |
initCurve( | |
cp = cp, | |
segmentDim = 4, | |
stepSize = 1, | |
degree = 3, | |
matrix = @[ | |
@[-t, 2.0 - t, t - 2.0, t], | |
@[2.0 * t, t - 3.0, 3.0 - (2.0 * t), -t], | |
@[-t, 0.0, t, 0.0], | |
@[0.0, 1.0, 0.0, 0.0], | |
], | |
) | |
func golden_curve*[T](cp: seq[T] or iterator (): T): Curve[T] | ICurve[T] = | |
## Golden curve, quadratic three point | |
## Three control points define a segment. | |
## The curve passes through all control point. | |
## At `t = 0.5` it passes through the middle control point. | |
## Number of control points in a multi segment curve is `3 + (n * 2)` | |
initCurve( | |
cp = cp, | |
segmentDim = 3, | |
stepSize = 2, | |
degree = 2, | |
matrix = @[ | |
@[2.0, -4.0, 2.0], | |
@[-3.0, 4.0, -1.0], | |
@[1.0] | |
] | |
) | |
func four_point3_curve*[T](cp: seq[T] or iterator (): T): Curve[T] | ICurve[T] = | |
## Cubic Four Point curve | |
## (identical to a quadratic Lagrange with `t1 = 1/3` and `t2 = 2/3`) | |
## Four control points define a segment. | |
## The curve passes through all control point. | |
## Number of control points in a multi segment curve is `4 + (n * 3)` | |
initCurve( | |
cp = cp, | |
segmentDim = 4, | |
stepSize = 3, | |
degree = 3, | |
matrix = divideMatrix( | |
@[ | |
@[-9.0, 27.0, -27.0, 9.0], | |
@[18.0, -45.0, 36.0, -9.0], | |
@[-11.0, 18.0, -9.0, 2.0], | |
@[2.0], | |
], | |
2.0, | |
), | |
) | |
func hermiteC*[T](cp: seq[T] or iterator (): T): Curve[T] | ICurve[T] = | |
## Hermite curve in conventional form | |
## Four control points define a segment. | |
## The curve passes through the first and second control point. The | |
## third and fourth control points are tangent vectors. | |
## Number of control points in a multi segment curve is `4 + (n * 3)` | |
## Not realy suitable for piece wise interpolation. | |
initCurve( | |
cp = cp, | |
segmentDim = 4, | |
stepSize = 3, | |
degree = 3, | |
matrix = @[ | |
@[2.0, -2.0, 1.0, 1.0], | |
@[-3.0, 3.0, -2.0, -1.0], | |
@[0.0, 0.0, 1.0], | |
@[1.0] | |
], | |
) | |
func hermite*[T](cp: seq[T] or iterator (): T, tension: float, bias: float): Curve[T] | ICurve[T] = | |
## Hermite curve (in Bezier form) | |
## Four control points define a segment. | |
## The curve passes through the first and last control point. The two | |
## middle control points are tangent vectors. | |
## Number of control points in a multi segment curve is `4 + (n * 1)` | |
initCurve( | |
cp = cp, | |
segmentDim = 4, | |
stepSize = 1, | |
degree = 3, | |
matrix = @[ | |
@[2.0, -3.0, 0.0, 1.0], | |
@[-2.0, 3.0, 0.0, 0.0], | |
@[tension + bias, tension - bias, -tension - (2.0 * bias), bias - tension], | |
@[bias - tension, tension - (2.0 * bias), bias, 0.0], | |
], | |
) | |
func catmullrom*[T](cp: seq[T] or iterator (): T): Curve[T] | ICurve[T] = | |
## Catmull Rom spline | |
## Four control points define a segment. | |
## The curve passes through the middle two control point. | |
## Number of control points in a multi segment curve is `4 + (n * 1)` | |
initCurve( | |
cp = cp, | |
segmentDim = 4, | |
stepSize = 1, | |
degree = 3, | |
matrix = divideMatrix( | |
@[ | |
@[-1.0, 3.0, -3.0, 1.0], | |
@[2.0, -5.0, 4.0, -1.0], | |
@[-1.0, 0.0, 1.0], | |
@[0.0, 2.0] | |
], | |
2.0, | |
), | |
) | |
func lagrange2*[T](cp: seq[T] or iterator (): T, t2: float): Curve[T] | ICurve[T] = | |
## Quadratic Lagrange curve | |
## Tree control points define a segment. | |
## The curve passes through all three control points. | |
## Number of control points in a multi segment curve is `3 + (n * 2)` | |
## `t2`: defines at what value for t the curve goes through P2 | |
let | |
n1 = 1.0 / t2 | |
n2 = 1.0 / (t2 * (t2 - 1.0)) | |
n3 = 1.0 / (1.0 - t2) | |
initCurve( | |
cp = cp, | |
segmentDim = 3, | |
stepSize = 2, | |
degree = 2, | |
matrix = @[ | |
@[n1, n2, n3], | |
@[-n1 - 1.0, -n2, -n3 * t2], | |
@[1.0] | |
] | |
) | |
# lagrange3 does not seem proper | |
#func lagrange3*[T](cp: seq[T] or iterator (): T, t1: float, t2: float): Curve[T] | ICurve[T] = | |
# ## Cubic Lagrange curve | |
# ## Four control points define a segement. | |
# ## Number of control points in a multi segment curve is `4 + (n * 3)` | |
# ## The value of `t1` determines at what value of t the curve passes through `P1`. | |
# ## The value of `t2` determines at what value of t the curve passes through `P2`. | |
# ## When `t1 = 1/3` and `t2 = 2/3`, this is the cubic four point. | |
# | |
# if t1 <= 0.0 or t2 >= 1.0 or t1 > t2: | |
# raise newException(CurveParameterError, "Not fulfilled: 0 < t1 < t2 < 1") | |
# | |
# let | |
# n0 = 1.0 / (t1 * t2) | |
# n1 = 1.0 / (t1 * (t1 - t2) * (t1 - 1)) | |
# n2 = 1.0 / (t2 * (t2 - t1) * (t2 - 1)) | |
# n3 = 1.0 / ((1 - t1) * (1 - t2)) | |
# | |
# initCurve( | |
# cp = cp, | |
# segmentDim = 4, | |
# stepSize = 3, | |
# degree = 3, | |
# matrix = @[ | |
# @[-n0, n1, n2, n3], | |
# @[n0 * (t1 + t2 + 1.0), -n1 * (t2 + 1.0), -n2 * (t1 + 1.0), -n3 * (t1 + t2)], | |
# @[-n0 * (t1 + t2 + t1 * t2), n1 * t2, n2 * t1, n3 * t1 * t2 ], | |
# @[ 1.0, 0.0, 0.0, 0.0] | |
# ] | |
# ) | |
func beta_spline*[T](cp: seq[T] or iterator (): T, tension: float, bias: float): Curve[T] | ICurve[T] = | |
## Beta spline, Cubic B-spline with Tension, Bias | |
## Four control points define a segment. | |
## The curve passes through none of the control points. It goes from near P1 to near P2. | |
## Number of control points in a multi segment curve is `4 + (n * 1)` | |
## `T`: from 0 to infinity, attracts to control point P1. From 0 to -6 pushes away from P1. | |
## `B`: from 0 to 1, pushes towards P2, from 1 to infinity pushes towards P1 | |
## `B = 1` & `T = 0` --> `B-spline` | |
initCurve( | |
cp = cp, | |
segmentDim = 4, | |
stepSize = 1, | |
degree = 3, | |
matrix = divideMatrix( | |
@[ | |
@[ | |
-2.0 * pow(bias, 3.0), | |
2.0 * (tension + pow(bias, 3.0) + pow(bias, 2.0) + bias), | |
-2.0 * (tension + pow(bias, 2.0) + bias + 1.0), | |
2.0, | |
], | |
@[ | |
6.0 * pow(bias, 3.0), | |
-3.0 * (tension + (2.0 * pow(bias, 3.0) + (2.0 * pow(bias, 2.0)))), | |
3.0 * (tension + (2.0 * pow(bias, 2.0))), | |
], | |
@[-6.0 * pow(bias, 3.0), 6.0 * (pow(bias, 3.0) - bias), 6.0 * bias], | |
@[2.0 * pow(bias, 3.0), tension + (4.0 * (pow(bias, 2.0) + bias)), 2.0], | |
], | |
(tension + (2.0 * pow(bias, 3.0)) + (4.0 * pow(bias, 2.0)) + (4.0 * bias) + 2.0), | |
), | |
) | |
func tcb_spline*[T]( | |
cp: seq[T] or iterator (): T, tension: float, cont: float, bias: float | |
): Curve[T] | ICurve[T] = | |
## Kochanek Bartels aka TCB-spline. Tension, Continuity, Bias. | |
## Four control points define a segment. | |
## The curve passes trough P1 and P2. | |
## Number of control points in a multi segment curve is 4 + (n * 1) | |
## Tension `T = +1` --> Tight, `T = -1` --> Round | |
## Bias `B = 1 --> Post Shoot`, `B = -1 --> Pre shoot` | |
## Continuity `C = +1 --> Inverted corners`, `C = -1 --> Box corners` | |
## `T = B = C --> Catmull Rom` | |
## `T = 1 --> simple cubic` | |
## `T = B = 0` & `C = -1 linear interpolation` | |
let | |
a = (1.0 - tension) * (1.0 + cont) * (1.0 + bias) | |
b = (1.0 - tension) * (1.0 - cont) * (1.0 - bias) | |
c = (1.0 - tension) * (1.0 - cont) * (1.0 + bias) | |
d = (1.0 - tension) * (1.0 + cont) * (1.0 - bias) | |
initCurve( | |
cp = cp, | |
segmentDim = 4, | |
stepSize = 1, | |
degree = 3, | |
matrix = divideMatrix( | |
@[ | |
@[-a, 4.0 + a - b - c, -4.0 + b + c - d, d], | |
@[2.0 * a, -6.0 - (2.0 * a) + (2.0 * b) + c, 6.0 - (2.0 * b) - c + d, -d], | |
@[-a, a - b, b], | |
@[0.0, 2.0], | |
], | |
2.0, | |
), | |
) | |
func palmer*[T](cp: seq[T] or iterator (): T, tension: float, bias: float): Curve[T] | ICurve[T] = | |
## Palmer with Tension, Bias | |
## Three control points define a segment. | |
## The curve passes trough P1 and P3 and passes by P2. | |
## Number of control points in a multi segment curve is `3 + (n * 2)` | |
let | |
n0 = 1.0 / bias | |
n1 = 1.0 / (bias * (1.0 - bias)) | |
n2 = 1.0 / (1.0 - bias) | |
initCurve( | |
cp = cp, | |
segmentDim = 3, | |
stepSize = 2, | |
degree = 3, | |
matrix = @[ | |
@[-2.0 * tension, 0.0, 2.0 * tension], | |
@[n0 + (3.0 * tension), -n1, n2 - (3.0 * tension)], | |
@[-1.0 - n0 - tension, n1, tension - (n2 / n0)], | |
@[1.0], | |
], | |
) | |
#[ | |
t goes from 0 - 1. From t figure out in what segment of the curve we are. | |
from the segment number and t figure out what the equivalent segment time tseg is. | |
segments are interpolated from 0-1, using tseg. | |
finaly run Horner | |
]# | |
func getPoint*[T](curve: Curve[T], t: float): T {.inline.}= | |
## Gets a single point from the curve, with t ∈ [0,1]. | |
var | |
segment = default(int) | |
tseg = default(float) | |
if t < 1.0: | |
segment = int(floor(t * float(curve.segments))) | |
tseg = (t * float(curve.segments)) - float(segment) | |
else: | |
segment = curve.segments - 1 | |
tseg = 1.0 | |
var F = curve.curmult[segment][0] | |
for i in 1 ..< len(curve.curmult[segment]): | |
F = (F * tseg) + curve.curmult[segment][i] | |
return F | |
func getCurve*[T](curve: Curve[T], resolution: int): seq[T] = | |
## Gets all points from the curve, with t ∈ [0, 1]. | |
result = newSeq[T](resolution) | |
for i in 0 ..< resolution: | |
let t = i / (resolution - 1) | |
result[i] = getPoint(curve, t) | |
func iterCurve*[T](curve: Curve[T], resolution: int): iterator (): T = | |
return iterator (): T = | |
for i in 0 ..< resolution: | |
let t = i / (resolution - 1) | |
yield getPoint(curve, t) | |
# A more unified approach to forward differencing | |
func setupForwardDifferences[T](curmult: var seq[T], delta: seq[float], degree: int): seq[T] = | |
result = newSeq[T](degree + 1) | |
# First element is the starting point | |
result[0] = pop(curmult) | |
# Calculate the forward differences based on the curve degree | |
for d in 1..degree: | |
var diff = default(T) | |
# Calculate coefficients for each degree | |
for i in 0..<(len(curmult) - (d - 1)): | |
# Determine the multiplier based on the position and degree | |
var multiplier = 1.0 | |
if d == 1: | |
# First derivative - use delta[i] | |
multiplier = delta[degree - 1] | |
elif d == 2: | |
# Second derivative - different coefficients based on position | |
multiplier = if i == 0: delta[degree - 2] * 6.0 | |
elif i == 1: delta[degree - 2] * 2.0 | |
else: delta[degree - 2] | |
elif d == 3: | |
# Third derivative - constant acceleration change | |
multiplier = delta[degree - 3] * 6.0 | |
diff += curmult[i] * multiplier | |
result[d] = diff | |
proc iterCurve*[T](curve: ICurve[T], steps: int): iterator (): T = | |
let fsteps = steps.float | |
# Create delta values based on steps | |
let delta = @[1 / (fsteps * fsteps * fsteps), 1 / (fsteps * fsteps), 1 / fsteps] | |
# Set up seq for segment, call curve.cp() iterator segmentDim times | |
var segment = newSeqWith(curve.segmentDim, curve.cp()) | |
return iterator (): T = | |
while true: | |
# Multiply the matrix coefficients by the control points | |
var curmult = newSeq[curve.origin](len(curve.matrix)) | |
for i in 0 ..< len(curve.matrix): | |
for j in 0 ..< len(curve.matrix[i]): | |
curmult[i] = curmult[i] + (segment[j] * curve.matrix[i][j]) | |
# Set up vectors for forward differences using the consolidated function | |
var iterCurves = setupForwardDifferences(curmult, delta, curve.degree) | |
# Step interpolate through one segment | |
var i = 0 | |
while i < steps: | |
yield iterCurves[0] | |
# Update using forward differences | |
for d in 0..<(curve.degree): | |
iterCurves[d] += iterCurves[d + 1] | |
inc(i) | |
# Get the points for the next segment | |
segment.delete(0..(curve.stepsize - 1)) | |
while segment.len != curve.segmentDim: | |
if finished(curve.cp): break | |
segment.add(curve.cp()) | |
# For the last segment, also yield its last point | |
if finished(curve.cp): | |
yield iterCurves[0] | |
break | |
when is_main_module: | |
import vmath | |
import pixie | |
func vals(data: seq): auto = | |
return iterator (): auto = | |
for i in data: | |
yield i | |
let | |
image = newImage(1000, 1000) | |
ctx = newContext(image) | |
v0: seq[Vec2] = @[ | |
vec2(150, 900), | |
vec2(150, 150), | |
vec2(500, 150), | |
vec2(500, 500), | |
vec2(500, 850), | |
vec2(850, 850), | |
vec2(850, 150) | |
] | |
resolution = 100 | |
steps = 50 | |
# control points | |
image.fill(rgba(255, 255, 255, 255)) | |
ctx.fillStyle = rgba(255, 0, 0, 255) | |
for i in v0: | |
ctx.fillCircle(circle(i, 8)) | |
# loop over seq of control points, get point by point | |
ctx.fillStyle = rgba(0, 255, 0, 125) | |
for i in 0 ..< resolution: | |
let t = i / (resolution - 1) | |
ctx.fillCircle( | |
circle( | |
getPoint[Vec2](bezier3[Vec2](v0), t), | |
9 | |
) | |
) | |
# loop over all points of curve | |
ctx.strokeStyle = rgba(125, 0, 125, 125) | |
ctx.lineWidth = 3 | |
let ps = getCurve[Vec2](bezier3[Vec2](v0), resolution) | |
for p in ps: | |
ctx.strokeCircle( | |
circle(p, 12) | |
) | |
# iterate using seq of control points as input to curve | |
ctx.fillStyle = rgba(255, 0, 0, 125) | |
let val = iterCurve[Vec2](bezier3[Vec2](v0), resolution) | |
for p in val(): | |
ctx.fillCircle( | |
circle(p, 6) | |
) | |
# iterate using seq of control points as input to curve | |
ctx.fillStyle = rgba(255,255, 255, 125) | |
let ip = iterCurve[Vec2](bezier3[Vec2](v0), resolution) | |
while true: | |
let val = ip() | |
if finished(ip): | |
break | |
ctx.fillCircle( | |
circle(val, 3) | |
) | |
# iterate using iterator of control points as input to curve | |
let pp = iterCurve[Vec2](bezier3[Vec2](vals(v0)), steps) | |
ctx.fillStyle = rgba(255, 0, 0, 255) | |
while true: | |
if finished(pp): | |
break | |
ctx.fillCircle( | |
circle(pp(), 2) | |
) | |
image.writeFile("curve_img.png") |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#nim c -d:ThreadPoolSize=8 -d:useMalloc -d:FixedChanSize=16 parcurve.nim | |
import std/[monotimes] | |
import curve | |
import pixie | |
import malebolgia | |
let | |
image = newImage(1000, 1000) | |
ctx = newContext(image) | |
v0 = @[ | |
vec2(100, 500), | |
vec2(100, 100), | |
vec2(500, 100), | |
vec2(500, 500), | |
vec2(500, 900), | |
vec2(900, 900), | |
vec2(900, 500), | |
] | |
resolution = 100000 | |
let tlin = bezier3(v0) | |
var m = createMaster() | |
var p = newSeq[Vec2](resolution) | |
func pcurve(curve:Curve, resolution: int, threadId: int, p: ptr): auto= | |
let numPer = resolution div ThreadPoolSize | |
let frm = numPer * threadId | |
let to = if threadId == ThreadPoolSize: | |
resolution - 1 | |
else: | |
numPer * (threadId + 1) | |
for idx in frm ..< to: | |
let t = idx / (resolution - 1) | |
p[][idx] = getPoint(curve, t) | |
let t0 = getMonoTime() | |
m.awaitAll: | |
for i in 0 ..< ThreadPoolSize: | |
m.spawn pcurve(tlin, resolution, i, addr p) | |
let t1 = getMonoTime() | |
echo t1 - t0 | |
image.fill(rgba(255, 255, 255, 255)) | |
ctx.fillStyle = rgba(0, 0, 0, 255) | |
for i in p: | |
ctx.fillCircle(circle(i, 0.5)) | |
image.writeFile("pcurve_img.png") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment