UNPKG

4.79 kBJavaScriptView Raw
1/**
2 * Abstract base class of interpolants over parametric samples.
3 *
4 * The parameter domain is one dimensional, typically the time or a path
5 * along a curve defined by the data.
6 *
7 * The sample values can have any dimensionality and derived classes may
8 * apply special interpretations to the data.
9 *
10 * This class provides the interval seek in a Template Method, deferring
11 * the actual interpolation to derived classes.
12 *
13 * Time complexity is O(1) for linear access crossing at most two points
14 * and O(log N) for random access, where N is the number of positions.
15 *
16 * References:
17 *
18 * http://www.oodesign.com/template-method-pattern.html
19 *
20 * @author tschw
21 */
22
23function Interpolant( parameterPositions, sampleValues, sampleSize, resultBuffer ) {
24
25 this.parameterPositions = parameterPositions;
26 this._cachedIndex = 0;
27
28 this.resultBuffer = resultBuffer !== undefined ?
29 resultBuffer : new sampleValues.constructor( sampleSize );
30 this.sampleValues = sampleValues;
31 this.valueSize = sampleSize;
32
33}
34
35Object.assign( Interpolant.prototype, {
36
37 evaluate: function ( t ) {
38
39 var pp = this.parameterPositions,
40 i1 = this._cachedIndex,
41
42 t1 = pp[ i1 ],
43 t0 = pp[ i1 - 1 ];
44
45 validate_interval: {
46
47 seek: {
48
49 var right;
50
51 linear_scan: {
52
53 //- See http://jsperf.com/comparison-to-undefined/3
54 //- slower code:
55 //-
56 //- if ( t >= t1 || t1 === undefined ) {
57 forward_scan: if ( ! ( t < t1 ) ) {
58
59 for ( var giveUpAt = i1 + 2; ; ) {
60
61 if ( t1 === undefined ) {
62
63 if ( t < t0 ) break forward_scan;
64
65 // after end
66
67 i1 = pp.length;
68 this._cachedIndex = i1;
69 return this.afterEnd_( i1 - 1, t, t0 );
70
71 }
72
73 if ( i1 === giveUpAt ) break; // this loop
74
75 t0 = t1;
76 t1 = pp[ ++ i1 ];
77
78 if ( t < t1 ) {
79
80 // we have arrived at the sought interval
81 break seek;
82
83 }
84
85 }
86
87 // prepare binary search on the right side of the index
88 right = pp.length;
89 break linear_scan;
90
91 }
92
93 //- slower code:
94 //- if ( t < t0 || t0 === undefined ) {
95 if ( ! ( t >= t0 ) ) {
96
97 // looping?
98
99 var t1global = pp[ 1 ];
100
101 if ( t < t1global ) {
102
103 i1 = 2; // + 1, using the scan for the details
104 t0 = t1global;
105
106 }
107
108 // linear reverse scan
109
110 for ( var giveUpAt = i1 - 2; ; ) {
111
112 if ( t0 === undefined ) {
113
114 // before start
115
116 this._cachedIndex = 0;
117 return this.beforeStart_( 0, t, t1 );
118
119 }
120
121 if ( i1 === giveUpAt ) break; // this loop
122
123 t1 = t0;
124 t0 = pp[ -- i1 - 1 ];
125
126 if ( t >= t0 ) {
127
128 // we have arrived at the sought interval
129 break seek;
130
131 }
132
133 }
134
135 // prepare binary search on the left side of the index
136 right = i1;
137 i1 = 0;
138 break linear_scan;
139
140 }
141
142 // the interval is valid
143
144 break validate_interval;
145
146 } // linear scan
147
148 // binary search
149
150 while ( i1 < right ) {
151
152 var mid = ( i1 + right ) >>> 1;
153
154 if ( t < pp[ mid ] ) {
155
156 right = mid;
157
158 } else {
159
160 i1 = mid + 1;
161
162 }
163
164 }
165
166 t1 = pp[ i1 ];
167 t0 = pp[ i1 - 1 ];
168
169 // check boundary cases, again
170
171 if ( t0 === undefined ) {
172
173 this._cachedIndex = 0;
174 return this.beforeStart_( 0, t, t1 );
175
176 }
177
178 if ( t1 === undefined ) {
179
180 i1 = pp.length;
181 this._cachedIndex = i1;
182 return this.afterEnd_( i1 - 1, t0, t );
183
184 }
185
186 } // seek
187
188 this._cachedIndex = i1;
189
190 this.intervalChanged_( i1, t0, t1 );
191
192 } // validate_interval
193
194 return this.interpolate_( i1, t0, t, t1 );
195
196 },
197
198 settings: null, // optional, subclass-specific settings structure
199 // Note: The indirection allows central control of many interpolants.
200
201 // --- Protected interface
202
203 DefaultSettings_: {},
204
205 getSettings_: function () {
206
207 return this.settings || this.DefaultSettings_;
208
209 },
210
211 copySampleValue_: function ( index ) {
212
213 // copies a sample value to the result buffer
214
215 var result = this.resultBuffer,
216 values = this.sampleValues,
217 stride = this.valueSize,
218 offset = index * stride;
219
220 for ( var i = 0; i !== stride; ++ i ) {
221
222 result[ i ] = values[ offset + i ];
223
224 }
225
226 return result;
227
228 },
229
230 // Template methods for derived classes:
231
232 interpolate_: function ( /* i1, t0, t, t1 */ ) {
233
234 throw new Error( 'call to abstract method' );
235 // implementations shall return this.resultBuffer
236
237 },
238
239 intervalChanged_: function ( /* i1, t0, t1 */ ) {
240
241 // empty
242
243 }
244
245} );
246
247//!\ DECLARE ALIAS AFTER assign prototype !
248Object.assign( Interpolant.prototype, {
249
250 //( 0, t, t0 ), returns this.resultBuffer
251 beforeStart_: Interpolant.prototype.copySampleValue_,
252
253 //( N-1, tN-1, t ), returns this.resultBuffer
254 afterEnd_: Interpolant.prototype.copySampleValue_,
255
256} );
257
258
259export { Interpolant };