CurvePath.js 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. import { Curve } from './Curve.js';
  2. import * as Curves from '../curves/Curves.js';
  3. /**************************************************************
  4. * Curved Path - a curve path is simply a array of connected
  5. * curves, but retains the api of a curve
  6. **************************************************************/
  7. class CurvePath extends Curve {
  8. constructor() {
  9. super();
  10. this.type = 'CurvePath';
  11. this.curves = [];
  12. this.autoClose = false; // Automatically closes the path
  13. }
  14. add( curve ) {
  15. this.curves.push( curve );
  16. }
  17. closePath() {
  18. // Add a line curve if start and end of lines are not connected
  19. const startPoint = this.curves[ 0 ].getPoint( 0 );
  20. const endPoint = this.curves[ this.curves.length - 1 ].getPoint( 1 );
  21. if ( ! startPoint.equals( endPoint ) ) {
  22. this.curves.push( new Curves[ 'LineCurve' ]( endPoint, startPoint ) );
  23. }
  24. return this;
  25. }
  26. // To get accurate point with reference to
  27. // entire path distance at time t,
  28. // following has to be done:
  29. // 1. Length of each sub path have to be known
  30. // 2. Locate and identify type of curve
  31. // 3. Get t for the curve
  32. // 4. Return curve.getPointAt(t')
  33. getPoint( t, optionalTarget ) {
  34. const d = t * this.getLength();
  35. const curveLengths = this.getCurveLengths();
  36. let i = 0;
  37. // To think about boundaries points.
  38. while ( i < curveLengths.length ) {
  39. if ( curveLengths[ i ] >= d ) {
  40. const diff = curveLengths[ i ] - d;
  41. const curve = this.curves[ i ];
  42. const segmentLength = curve.getLength();
  43. const u = segmentLength === 0 ? 0 : 1 - diff / segmentLength;
  44. return curve.getPointAt( u, optionalTarget );
  45. }
  46. i ++;
  47. }
  48. return null;
  49. // loop where sum != 0, sum > d , sum+1 <d
  50. }
  51. // We cannot use the default THREE.Curve getPoint() with getLength() because in
  52. // THREE.Curve, getLength() depends on getPoint() but in THREE.CurvePath
  53. // getPoint() depends on getLength
  54. getLength() {
  55. const lens = this.getCurveLengths();
  56. return lens[ lens.length - 1 ];
  57. }
  58. // cacheLengths must be recalculated.
  59. updateArcLengths() {
  60. this.needsUpdate = true;
  61. this.cacheLengths = null;
  62. this.getCurveLengths();
  63. }
  64. // Compute lengths and cache them
  65. // We cannot overwrite getLengths() because UtoT mapping uses it.
  66. getCurveLengths() {
  67. // We use cache values if curves and cache array are same length
  68. if ( this.cacheLengths && this.cacheLengths.length === this.curves.length ) {
  69. return this.cacheLengths;
  70. }
  71. // Get length of sub-curve
  72. // Push sums into cached array
  73. const lengths = [];
  74. let sums = 0;
  75. for ( let i = 0, l = this.curves.length; i < l; i ++ ) {
  76. sums += this.curves[ i ].getLength();
  77. lengths.push( sums );
  78. }
  79. this.cacheLengths = lengths;
  80. return lengths;
  81. }
  82. getSpacedPoints( divisions = 40 ) {
  83. const points = [];
  84. for ( let i = 0; i <= divisions; i ++ ) {
  85. points.push( this.getPoint( i / divisions ) );
  86. }
  87. if ( this.autoClose ) {
  88. points.push( points[ 0 ] );
  89. }
  90. return points;
  91. }
  92. getPoints( divisions = 12 ) {
  93. const points = [];
  94. let last;
  95. for ( let i = 0, curves = this.curves; i < curves.length; i ++ ) {
  96. const curve = curves[ i ];
  97. const resolution = curve.isEllipseCurve ? divisions * 2
  98. : ( curve.isLineCurve || curve.isLineCurve3 ) ? 1
  99. : curve.isSplineCurve ? divisions * curve.points.length
  100. : divisions;
  101. const pts = curve.getPoints( resolution );
  102. for ( let j = 0; j < pts.length; j ++ ) {
  103. const point = pts[ j ];
  104. if ( last && last.equals( point ) ) continue; // ensures no consecutive points are duplicates
  105. points.push( point );
  106. last = point;
  107. }
  108. }
  109. if ( this.autoClose && points.length > 1 && ! points[ points.length - 1 ].equals( points[ 0 ] ) ) {
  110. points.push( points[ 0 ] );
  111. }
  112. return points;
  113. }
  114. copy( source ) {
  115. super.copy( source );
  116. this.curves = [];
  117. for ( let i = 0, l = source.curves.length; i < l; i ++ ) {
  118. const curve = source.curves[ i ];
  119. this.curves.push( curve.clone() );
  120. }
  121. this.autoClose = source.autoClose;
  122. return this;
  123. }
  124. toJSON() {
  125. const data = super.toJSON();
  126. data.autoClose = this.autoClose;
  127. data.curves = [];
  128. for ( let i = 0, l = this.curves.length; i < l; i ++ ) {
  129. const curve = this.curves[ i ];
  130. data.curves.push( curve.toJSON() );
  131. }
  132. return data;
  133. }
  134. fromJSON( json ) {
  135. super.fromJSON( json );
  136. this.autoClose = json.autoClose;
  137. this.curves = [];
  138. for ( let i = 0, l = json.curves.length; i < l; i ++ ) {
  139. const curve = json.curves[ i ];
  140. this.curves.push( new Curves[ curve.type ]().fromJSON( curve ) );
  141. }
  142. return this;
  143. }
  144. }
  145. export { CurvePath };