CatmullRomCurve3.js 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. import { Vector3 } from '../../math/Vector3.js';
  2. import { Curve } from '../core/Curve.js';
  3. /**
  4. * Centripetal CatmullRom Curve - which is useful for avoiding
  5. * cusps and self-intersections in non-uniform catmull rom curves.
  6. * http://www.cemyuksel.com/research/catmullrom_param/catmullrom.pdf
  7. *
  8. * curve.type accepts centripetal(default), chordal and catmullrom
  9. * curve.tension is used for catmullrom which defaults to 0.5
  10. */
  11. /*
  12. Based on an optimized c++ solution in
  13. - http://stackoverflow.com/questions/9489736/catmull-rom-curve-with-no-cusps-and-no-self-intersections/
  14. - http://ideone.com/NoEbVM
  15. This CubicPoly class could be used for reusing some variables and calculations,
  16. but for three.js curve use, it could be possible inlined and flatten into a single function call
  17. which can be placed in CurveUtils.
  18. */
  19. function CubicPoly() {
  20. let c0 = 0, c1 = 0, c2 = 0, c3 = 0;
  21. /*
  22. * Compute coefficients for a cubic polynomial
  23. * p(s) = c0 + c1*s + c2*s^2 + c3*s^3
  24. * such that
  25. * p(0) = x0, p(1) = x1
  26. * and
  27. * p'(0) = t0, p'(1) = t1.
  28. */
  29. function init( x0, x1, t0, t1 ) {
  30. c0 = x0;
  31. c1 = t0;
  32. c2 = - 3 * x0 + 3 * x1 - 2 * t0 - t1;
  33. c3 = 2 * x0 - 2 * x1 + t0 + t1;
  34. }
  35. return {
  36. initCatmullRom: function ( x0, x1, x2, x3, tension ) {
  37. init( x1, x2, tension * ( x2 - x0 ), tension * ( x3 - x1 ) );
  38. },
  39. initNonuniformCatmullRom: function ( x0, x1, x2, x3, dt0, dt1, dt2 ) {
  40. // compute tangents when parameterized in [t1,t2]
  41. let t1 = ( x1 - x0 ) / dt0 - ( x2 - x0 ) / ( dt0 + dt1 ) + ( x2 - x1 ) / dt1;
  42. let t2 = ( x2 - x1 ) / dt1 - ( x3 - x1 ) / ( dt1 + dt2 ) + ( x3 - x2 ) / dt2;
  43. // rescale tangents for parametrization in [0,1]
  44. t1 *= dt1;
  45. t2 *= dt1;
  46. init( x1, x2, t1, t2 );
  47. },
  48. calc: function ( t ) {
  49. const t2 = t * t;
  50. const t3 = t2 * t;
  51. return c0 + c1 * t + c2 * t2 + c3 * t3;
  52. }
  53. };
  54. }
  55. //
  56. const tmp = new Vector3();
  57. const px = new CubicPoly(), py = new CubicPoly(), pz = new CubicPoly();
  58. function CatmullRomCurve3( points, closed, curveType, tension ) {
  59. Curve.call( this );
  60. this.type = 'CatmullRomCurve3';
  61. this.points = points || [];
  62. this.closed = closed || false;
  63. this.curveType = curveType || 'centripetal';
  64. this.tension = ( tension !== undefined ) ? tension : 0.5;
  65. }
  66. CatmullRomCurve3.prototype = Object.create( Curve.prototype );
  67. CatmullRomCurve3.prototype.constructor = CatmullRomCurve3;
  68. CatmullRomCurve3.prototype.isCatmullRomCurve3 = true;
  69. CatmullRomCurve3.prototype.getPoint = function ( t, optionalTarget ) {
  70. const point = optionalTarget || new Vector3();
  71. const points = this.points;
  72. const l = points.length;
  73. const p = ( l - ( this.closed ? 0 : 1 ) ) * t;
  74. let intPoint = Math.floor( p );
  75. let weight = p - intPoint;
  76. if ( this.closed ) {
  77. intPoint += intPoint > 0 ? 0 : ( Math.floor( Math.abs( intPoint ) / l ) + 1 ) * l;
  78. } else if ( weight === 0 && intPoint === l - 1 ) {
  79. intPoint = l - 2;
  80. weight = 1;
  81. }
  82. let p0, p3; // 4 points (p1 & p2 defined below)
  83. if ( this.closed || intPoint > 0 ) {
  84. p0 = points[ ( intPoint - 1 ) % l ];
  85. } else {
  86. // extrapolate first point
  87. tmp.subVectors( points[ 0 ], points[ 1 ] ).add( points[ 0 ] );
  88. p0 = tmp;
  89. }
  90. const p1 = points[ intPoint % l ];
  91. const p2 = points[ ( intPoint + 1 ) % l ];
  92. if ( this.closed || intPoint + 2 < l ) {
  93. p3 = points[ ( intPoint + 2 ) % l ];
  94. } else {
  95. // extrapolate last point
  96. tmp.subVectors( points[ l - 1 ], points[ l - 2 ] ).add( points[ l - 1 ] );
  97. p3 = tmp;
  98. }
  99. if ( this.curveType === 'centripetal' || this.curveType === 'chordal' ) {
  100. // init Centripetal / Chordal Catmull-Rom
  101. const pow = this.curveType === 'chordal' ? 0.5 : 0.25;
  102. let dt0 = Math.pow( p0.distanceToSquared( p1 ), pow );
  103. let dt1 = Math.pow( p1.distanceToSquared( p2 ), pow );
  104. let dt2 = Math.pow( p2.distanceToSquared( p3 ), pow );
  105. // safety check for repeated points
  106. if ( dt1 < 1e-4 ) dt1 = 1.0;
  107. if ( dt0 < 1e-4 ) dt0 = dt1;
  108. if ( dt2 < 1e-4 ) dt2 = dt1;
  109. px.initNonuniformCatmullRom( p0.x, p1.x, p2.x, p3.x, dt0, dt1, dt2 );
  110. py.initNonuniformCatmullRom( p0.y, p1.y, p2.y, p3.y, dt0, dt1, dt2 );
  111. pz.initNonuniformCatmullRom( p0.z, p1.z, p2.z, p3.z, dt0, dt1, dt2 );
  112. } else if ( this.curveType === 'catmullrom' ) {
  113. px.initCatmullRom( p0.x, p1.x, p2.x, p3.x, this.tension );
  114. py.initCatmullRom( p0.y, p1.y, p2.y, p3.y, this.tension );
  115. pz.initCatmullRom( p0.z, p1.z, p2.z, p3.z, this.tension );
  116. }
  117. point.set(
  118. px.calc( weight ),
  119. py.calc( weight ),
  120. pz.calc( weight )
  121. );
  122. return point;
  123. };
  124. CatmullRomCurve3.prototype.copy = function ( source ) {
  125. Curve.prototype.copy.call( this, source );
  126. this.points = [];
  127. for ( let i = 0, l = source.points.length; i < l; i ++ ) {
  128. const point = source.points[ i ];
  129. this.points.push( point.clone() );
  130. }
  131. this.closed = source.closed;
  132. this.curveType = source.curveType;
  133. this.tension = source.tension;
  134. return this;
  135. };
  136. CatmullRomCurve3.prototype.toJSON = function () {
  137. const data = Curve.prototype.toJSON.call( this );
  138. data.points = [];
  139. for ( let i = 0, l = this.points.length; i < l; i ++ ) {
  140. const point = this.points[ i ];
  141. data.points.push( point.toArray() );
  142. }
  143. data.closed = this.closed;
  144. data.curveType = this.curveType;
  145. data.tension = this.tension;
  146. return data;
  147. };
  148. CatmullRomCurve3.prototype.fromJSON = function ( json ) {
  149. Curve.prototype.fromJSON.call( this, json );
  150. this.points = [];
  151. for ( let i = 0, l = json.points.length; i < l; i ++ ) {
  152. const point = json.points[ i ];
  153. this.points.push( new Vector3().fromArray( point ) );
  154. }
  155. this.closed = json.closed;
  156. this.curveType = json.curveType;
  157. this.tension = json.tension;
  158. return this;
  159. };
  160. export { CatmullRomCurve3 };