File size: 2,905 Bytes
2b7aae2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
import { ZeroCurvatureEnding } from '../../constants.js';
import { Interpolant } from '../Interpolant.js';
import { WrapAroundEnding, ZeroSlopeEnding } from '../../constants.js';

/**
 * Fast and simple cubic spline interpolant.
 *
 * It was derived from a Hermitian construction setting the first derivative
 * at each sample position to the linear slope between neighboring positions
 * over their parameter interval.
 */

class CubicInterpolant extends Interpolant {
	constructor(parameterPositions, sampleValues, sampleSize, resultBuffer) {
		super(parameterPositions, sampleValues, sampleSize, resultBuffer);

		this._weightPrev = -0;
		this._offsetPrev = -0;
		this._weightNext = -0;
		this._offsetNext = -0;

		this.DefaultSettings_ = {
			endingStart: ZeroCurvatureEnding,
			endingEnd: ZeroCurvatureEnding,
		};
	}

	intervalChanged_(i1, t0, t1) {
		const pp = this.parameterPositions;
		let iPrev = i1 - 2,
			iNext = i1 + 1,
			tPrev = pp[iPrev],
			tNext = pp[iNext];

		if (tPrev === undefined) {
			switch (this.getSettings_().endingStart) {
				case ZeroSlopeEnding:
					// f'(t0) = 0
					iPrev = i1;
					tPrev = 2 * t0 - t1;

					break;

				case WrapAroundEnding:
					// use the other end of the curve
					iPrev = pp.length - 2;
					tPrev = t0 + pp[iPrev] - pp[iPrev + 1];

					break;

				default:
					// ZeroCurvatureEnding

					// f''(t0) = 0 a.k.a. Natural Spline
					iPrev = i1;
					tPrev = t1;
			}
		}

		if (tNext === undefined) {
			switch (this.getSettings_().endingEnd) {
				case ZeroSlopeEnding:
					// f'(tN) = 0
					iNext = i1;
					tNext = 2 * t1 - t0;

					break;

				case WrapAroundEnding:
					// use the other end of the curve
					iNext = 1;
					tNext = t1 + pp[1] - pp[0];

					break;

				default:
					// ZeroCurvatureEnding

					// f''(tN) = 0, a.k.a. Natural Spline
					iNext = i1 - 1;
					tNext = t0;
			}
		}

		const halfDt = (t1 - t0) * 0.5,
			stride = this.valueSize;

		this._weightPrev = halfDt / (t0 - tPrev);
		this._weightNext = halfDt / (tNext - t1);
		this._offsetPrev = iPrev * stride;
		this._offsetNext = iNext * stride;
	}

	interpolate_(i1, t0, t, t1) {
		const result = this.resultBuffer,
			values = this.sampleValues,
			stride = this.valueSize,
			o1 = i1 * stride,
			o0 = o1 - stride,
			oP = this._offsetPrev,
			oN = this._offsetNext,
			wP = this._weightPrev,
			wN = this._weightNext,
			p = (t - t0) / (t1 - t0),
			pp = p * p,
			ppp = pp * p;

		// evaluate polynomials

		const sP = -wP * ppp + 2 * wP * pp - wP * p;
		const s0 = (1 + wP) * ppp + (-1.5 - 2 * wP) * pp + (-0.5 + wP) * p + 1;
		const s1 = (-1 - wN) * ppp + (1.5 + wN) * pp + 0.5 * p;
		const sN = wN * ppp - wN * pp;

		// combine data linearly

		for (let i = 0; i !== stride; ++i) {
			result[i] = sP * values[oP + i] + s0 * values[o0 + i] + s1 * values[o1 + i] + sN * values[oN + i];
		}

		return result;
	}
}

export { CubicInterpolant };