File size: 4,309 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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
/**
 * Abstract base class of interpolants over parametric samples.
 *
 * The parameter domain is one dimensional, typically the time or a path
 * along a curve defined by the data.
 *
 * The sample values can have any dimensionality and derived classes may
 * apply special interpretations to the data.
 *
 * This class provides the interval seek in a Template Method, deferring
 * the actual interpolation to derived classes.
 *
 * Time complexity is O(1) for linear access crossing at most two points
 * and O(log N) for random access, where N is the number of positions.
 *
 * References:
 *
 * 		http://www.oodesign.com/template-method-pattern.html
 *
 */

class Interpolant {
	constructor(parameterPositions, sampleValues, sampleSize, resultBuffer) {
		this.parameterPositions = parameterPositions;
		this._cachedIndex = 0;

		this.resultBuffer = resultBuffer !== undefined ? resultBuffer : new sampleValues.constructor(sampleSize);
		this.sampleValues = sampleValues;
		this.valueSize = sampleSize;

		this.settings = null;
		this.DefaultSettings_ = {};
	}

	evaluate(t) {
		const pp = this.parameterPositions;
		let i1 = this._cachedIndex,
			t1 = pp[i1],
			t0 = pp[i1 - 1];

		validate_interval: {
			seek: {
				let right;

				linear_scan: {
					//- See http://jsperf.com/comparison-to-undefined/3
					//- slower code:
					//-
					//- 				if ( t >= t1 || t1 === undefined ) {
					forward_scan: if (!(t < t1)) {
						for (let giveUpAt = i1 + 2; ; ) {
							if (t1 === undefined) {
								if (t < t0) break forward_scan;

								// after end

								i1 = pp.length;
								this._cachedIndex = i1;
								return this.afterEnd_(i1 - 1, t, t0);
							}

							if (i1 === giveUpAt) break; // this loop

							t0 = t1;
							t1 = pp[++i1];

							if (t < t1) {
								// we have arrived at the sought interval
								break seek;
							}
						}

						// prepare binary search on the right side of the index
						right = pp.length;
						break linear_scan;
					}

					//- slower code:
					//-					if ( t < t0 || t0 === undefined ) {
					if (!(t >= t0)) {
						// looping?

						const t1global = pp[1];

						if (t < t1global) {
							i1 = 2; // + 1, using the scan for the details
							t0 = t1global;
						}

						// linear reverse scan

						for (let giveUpAt = i1 - 2; ; ) {
							if (t0 === undefined) {
								// before start

								this._cachedIndex = 0;
								return this.beforeStart_(0, t, t1);
							}

							if (i1 === giveUpAt) break; // this loop

							t1 = t0;
							t0 = pp[--i1 - 1];

							if (t >= t0) {
								// we have arrived at the sought interval
								break seek;
							}
						}

						// prepare binary search on the left side of the index
						right = i1;
						i1 = 0;
						break linear_scan;
					}

					// the interval is valid

					break validate_interval;
				} // linear scan

				// binary search

				while (i1 < right) {
					const mid = (i1 + right) >>> 1;

					if (t < pp[mid]) {
						right = mid;
					} else {
						i1 = mid + 1;
					}
				}

				t1 = pp[i1];
				t0 = pp[i1 - 1];

				// check boundary cases, again

				if (t0 === undefined) {
					this._cachedIndex = 0;
					return this.beforeStart_(0, t, t1);
				}

				if (t1 === undefined) {
					i1 = pp.length;
					this._cachedIndex = i1;
					return this.afterEnd_(i1 - 1, t0, t);
				}
			} // seek

			this._cachedIndex = i1;

			this.intervalChanged_(i1, t0, t1);
		} // validate_interval

		return this.interpolate_(i1, t0, t, t1);
	}

	getSettings_() {
		return this.settings || this.DefaultSettings_;
	}

	copySampleValue_(index) {
		// copies a sample value to the result buffer

		const result = this.resultBuffer,
			values = this.sampleValues,
			stride = this.valueSize,
			offset = index * stride;

		for (let i = 0; i !== stride; ++i) {
			result[i] = values[offset + i];
		}

		return result;
	}

	// Template methods for derived classes:

	interpolate_(/* i1, t0, t, t1 */) {
		throw new Error('call to abstract method');
		// implementations shall return this.resultBuffer
	}

	intervalChanged_(/* i1, t0, t1 */) {
		// empty
	}
}

// ALIAS DEFINITIONS

Interpolant.prototype.beforeStart_ = Interpolant.prototype.copySampleValue_;
Interpolant.prototype.afterEnd_ = Interpolant.prototype.copySampleValue_;

export { Interpolant };