Interpolant.js 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  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. function Interpolant( parameterPositions, sampleValues, sampleSize, resultBuffer ) {
  23. this.parameterPositions = parameterPositions;
  24. this._cachedIndex = 0;
  25. this.resultBuffer = resultBuffer !== undefined ?
  26. resultBuffer : new sampleValues.constructor( sampleSize );
  27. this.sampleValues = sampleValues;
  28. this.valueSize = sampleSize;
  29. }
  30. Object.assign( Interpolant.prototype, {
  31. evaluate: function ( t ) {
  32. var pp = this.parameterPositions,
  33. i1 = this._cachedIndex,
  34. t1 = pp[ i1 ],
  35. t0 = pp[ i1 - 1 ];
  36. validate_interval: {
  37. seek: {
  38. var right;
  39. linear_scan: {
  40. //- See http://jsperf.com/comparison-to-undefined/3
  41. //- slower code:
  42. //-
  43. //- if ( t >= t1 || t1 === undefined ) {
  44. forward_scan: if ( ! ( t < t1 ) ) {
  45. for ( var giveUpAt = i1 + 2; ; ) {
  46. if ( t1 === undefined ) {
  47. if ( t < t0 ) break forward_scan;
  48. // after end
  49. i1 = pp.length;
  50. this._cachedIndex = i1;
  51. return this.afterEnd_( i1 - 1, t, t0 );
  52. }
  53. if ( i1 === giveUpAt ) break; // this loop
  54. t0 = t1;
  55. t1 = pp[ ++ i1 ];
  56. if ( t < t1 ) {
  57. // we have arrived at the sought interval
  58. break seek;
  59. }
  60. }
  61. // prepare binary search on the right side of the index
  62. right = pp.length;
  63. break linear_scan;
  64. }
  65. //- slower code:
  66. //- if ( t < t0 || t0 === undefined ) {
  67. if ( ! ( t >= t0 ) ) {
  68. // looping?
  69. var t1global = pp[ 1 ];
  70. if ( t < t1global ) {
  71. i1 = 2; // + 1, using the scan for the details
  72. t0 = t1global;
  73. }
  74. // linear reverse scan
  75. for ( var giveUpAt = i1 - 2; ; ) {
  76. if ( t0 === undefined ) {
  77. // before start
  78. this._cachedIndex = 0;
  79. return this.beforeStart_( 0, t, t1 );
  80. }
  81. if ( i1 === giveUpAt ) break; // this loop
  82. t1 = t0;
  83. t0 = pp[ -- i1 - 1 ];
  84. if ( t >= t0 ) {
  85. // we have arrived at the sought interval
  86. break seek;
  87. }
  88. }
  89. // prepare binary search on the left side of the index
  90. right = i1;
  91. i1 = 0;
  92. break linear_scan;
  93. }
  94. // the interval is valid
  95. break validate_interval;
  96. } // linear scan
  97. // binary search
  98. while ( i1 < right ) {
  99. var mid = ( i1 + right ) >>> 1;
  100. if ( t < pp[ mid ] ) {
  101. right = mid;
  102. } else {
  103. i1 = mid + 1;
  104. }
  105. }
  106. t1 = pp[ i1 ];
  107. t0 = pp[ i1 - 1 ];
  108. // check boundary cases, again
  109. if ( t0 === undefined ) {
  110. this._cachedIndex = 0;
  111. return this.beforeStart_( 0, t, t1 );
  112. }
  113. if ( t1 === undefined ) {
  114. i1 = pp.length;
  115. this._cachedIndex = i1;
  116. return this.afterEnd_( i1 - 1, t0, t );
  117. }
  118. } // seek
  119. this._cachedIndex = i1;
  120. this.intervalChanged_( i1, t0, t1 );
  121. } // validate_interval
  122. return this.interpolate_( i1, t0, t, t1 );
  123. },
  124. settings: null, // optional, subclass-specific settings structure
  125. // Note: The indirection allows central control of many interpolants.
  126. // --- Protected interface
  127. DefaultSettings_: {},
  128. getSettings_: function () {
  129. return this.settings || this.DefaultSettings_;
  130. },
  131. copySampleValue_: function ( index ) {
  132. // copies a sample value to the result buffer
  133. var result = this.resultBuffer,
  134. values = this.sampleValues,
  135. stride = this.valueSize,
  136. offset = index * stride;
  137. for ( var i = 0; i !== stride; ++ i ) {
  138. result[ i ] = values[ offset + i ];
  139. }
  140. return result;
  141. },
  142. // Template methods for derived classes:
  143. interpolate_: function ( /* i1, t0, t, t1 */ ) {
  144. throw new Error( 'call to abstract method' );
  145. // implementations shall return this.resultBuffer
  146. },
  147. intervalChanged_: function ( /* i1, t0, t1 */ ) {
  148. // empty
  149. }
  150. } );
  151. // DECLARE ALIAS AFTER assign prototype
  152. Object.assign( Interpolant.prototype, {
  153. //( 0, t, t0 ), returns this.resultBuffer
  154. beforeStart_: Interpolant.prototype.copySampleValue_,
  155. //( N-1, tN-1, t ), returns this.resultBuffer
  156. afterEnd_: Interpolant.prototype.copySampleValue_,
  157. } );
  158. export { Interpolant };