Curve.js 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386
  1. import { _Math } from '../../math/Math';
  2. import { Vector3 } from '../../math/Vector3';
  3. import { Matrix4 } from '../../math/Matrix4';
  4. /**
  5. * @author zz85 / http://www.lab4games.net/zz85/blog
  6. * Extensible curve object
  7. *
  8. * Some common of Curve methods
  9. * .getPoint(t), getTangent(t)
  10. * .getPointAt(u), getTangentAt(u)
  11. * .getPoints(), .getSpacedPoints()
  12. * .getLength()
  13. * .updateArcLengths()
  14. *
  15. * This following classes subclasses THREE.Curve:
  16. *
  17. * -- 2d classes --
  18. * THREE.LineCurve
  19. * THREE.QuadraticBezierCurve
  20. * THREE.CubicBezierCurve
  21. * THREE.SplineCurve
  22. * THREE.ArcCurve
  23. * THREE.EllipseCurve
  24. *
  25. * -- 3d classes --
  26. * THREE.LineCurve3
  27. * THREE.QuadraticBezierCurve3
  28. * THREE.CubicBezierCurve3
  29. * THREE.CatmullRomCurve3
  30. *
  31. * A series of curves can be represented as a THREE.CurvePath
  32. *
  33. **/
  34. /**************************************************************
  35. * Abstract Curve base class
  36. **************************************************************/
  37. function Curve() {}
  38. Object.assign( Curve.prototype, {
  39. // Virtual base class method to overwrite and implement in subclasses
  40. // - t [0 .. 1]
  41. getPoint: function ( t ) {
  42. console.warn( "THREE.Curve: Warning, getPoint() not implemented!" );
  43. return null;
  44. },
  45. // Get point at relative position in curve according to arc length
  46. // - u [0 .. 1]
  47. getPointAt: function ( u ) {
  48. var t = this.getUtoTmapping( u );
  49. return this.getPoint( t );
  50. },
  51. // Get sequence of points using getPoint( t )
  52. getPoints: function ( divisions ) {
  53. if ( divisions === undefined ) divisions = 5;
  54. var points = [];
  55. for ( var d = 0; d <= divisions; d ++ ) {
  56. points.push( this.getPoint( d / divisions ) );
  57. }
  58. return points;
  59. },
  60. // Get sequence of points using getPointAt( u )
  61. getSpacedPoints: function ( divisions ) {
  62. if ( divisions === undefined ) divisions = 5;
  63. var points = [];
  64. for ( var d = 0; d <= divisions; d ++ ) {
  65. points.push( this.getPointAt( d / divisions ) );
  66. }
  67. return points;
  68. },
  69. // Get total curve arc length
  70. getLength: function () {
  71. var lengths = this.getLengths();
  72. return lengths[ lengths.length - 1 ];
  73. },
  74. // Get list of cumulative segment lengths
  75. getLengths: function ( divisions ) {
  76. if ( divisions === undefined ) divisions = ( this.__arcLengthDivisions ) ? ( this.__arcLengthDivisions ) : 200;
  77. if ( this.cacheArcLengths
  78. && ( this.cacheArcLengths.length === divisions + 1 )
  79. && ! this.needsUpdate ) {
  80. //console.log( "cached", this.cacheArcLengths );
  81. return this.cacheArcLengths;
  82. }
  83. this.needsUpdate = false;
  84. var cache = [];
  85. var current, last = this.getPoint( 0 );
  86. var p, sum = 0;
  87. cache.push( 0 );
  88. for ( p = 1; p <= divisions; p ++ ) {
  89. current = this.getPoint ( p / divisions );
  90. sum += current.distanceTo( last );
  91. cache.push( sum );
  92. last = current;
  93. }
  94. this.cacheArcLengths = cache;
  95. return cache; // { sums: cache, sum:sum }; Sum is in the last element.
  96. },
  97. updateArcLengths: function() {
  98. this.needsUpdate = true;
  99. this.getLengths();
  100. },
  101. // Given u ( 0 .. 1 ), get a t to find p. This gives you points which are equidistant
  102. getUtoTmapping: function ( u, distance ) {
  103. var arcLengths = this.getLengths();
  104. var i = 0, il = arcLengths.length;
  105. var targetArcLength; // The targeted u distance value to get
  106. if ( distance ) {
  107. targetArcLength = distance;
  108. } else {
  109. targetArcLength = u * arcLengths[ il - 1 ];
  110. }
  111. //var time = Date.now();
  112. // binary search for the index with largest value smaller than target u distance
  113. var low = 0, high = il - 1, comparison;
  114. while ( low <= high ) {
  115. i = Math.floor( low + ( high - low ) / 2 ); // less likely to overflow, though probably not issue here, JS doesn't really have integers, all numbers are floats
  116. comparison = arcLengths[ i ] - targetArcLength;
  117. if ( comparison < 0 ) {
  118. low = i + 1;
  119. } else if ( comparison > 0 ) {
  120. high = i - 1;
  121. } else {
  122. high = i;
  123. break;
  124. // DONE
  125. }
  126. }
  127. i = high;
  128. //console.log('b' , i, low, high, Date.now()- time);
  129. if ( arcLengths[ i ] === targetArcLength ) {
  130. var t = i / ( il - 1 );
  131. return t;
  132. }
  133. // we could get finer grain at lengths, or use simple interpolation between two points
  134. var lengthBefore = arcLengths[ i ];
  135. var lengthAfter = arcLengths[ i + 1 ];
  136. var segmentLength = lengthAfter - lengthBefore;
  137. // determine where we are between the 'before' and 'after' points
  138. var segmentFraction = ( targetArcLength - lengthBefore ) / segmentLength;
  139. // add that fractional amount to t
  140. var t = ( i + segmentFraction ) / ( il - 1 );
  141. return t;
  142. },
  143. // Returns a unit vector tangent at t
  144. // In case any sub curve does not implement its tangent derivation,
  145. // 2 points a small delta apart will be used to find its gradient
  146. // which seems to give a reasonable approximation
  147. getTangent: function( t ) {
  148. var delta = 0.0001;
  149. var t1 = t - delta;
  150. var t2 = t + delta;
  151. // Capping in case of danger
  152. if ( t1 < 0 ) t1 = 0;
  153. if ( t2 > 1 ) t2 = 1;
  154. var pt1 = this.getPoint( t1 );
  155. var pt2 = this.getPoint( t2 );
  156. var vec = pt2.clone().sub( pt1 );
  157. return vec.normalize();
  158. },
  159. getTangentAt: function ( u ) {
  160. var t = this.getUtoTmapping( u );
  161. return this.getTangent( t );
  162. },
  163. computeFrenetFrames: function ( segments, closed ) {
  164. // see http://www.cs.indiana.edu/pub/techreports/TR425.pdf
  165. var normal = new Vector3();
  166. var tangents = [];
  167. var normals = [];
  168. var binormals = [];
  169. var vec = new Vector3();
  170. var mat = new Matrix4();
  171. var i, u, theta;
  172. // compute the tangent vectors for each segment on the curve
  173. for ( i = 0; i <= segments; i ++ ) {
  174. u = i / segments;
  175. tangents[ i ] = this.getTangentAt( u );
  176. tangents[ i ].normalize();
  177. }
  178. // select an initial normal vector perpendicular to the first tangent vector,
  179. // and in the direction of the minimum tangent xyz component
  180. normals[ 0 ] = new Vector3();
  181. binormals[ 0 ] = new Vector3();
  182. var min = Number.MAX_VALUE;
  183. var tx = Math.abs( tangents[ 0 ].x );
  184. var ty = Math.abs( tangents[ 0 ].y );
  185. var tz = Math.abs( tangents[ 0 ].z );
  186. if ( tx <= min ) {
  187. min = tx;
  188. normal.set( 1, 0, 0 );
  189. }
  190. if ( ty <= min ) {
  191. min = ty;
  192. normal.set( 0, 1, 0 );
  193. }
  194. if ( tz <= min ) {
  195. normal.set( 0, 0, 1 );
  196. }
  197. vec.crossVectors( tangents[ 0 ], normal ).normalize();
  198. normals[ 0 ].crossVectors( tangents[ 0 ], vec );
  199. binormals[ 0 ].crossVectors( tangents[ 0 ], normals[ 0 ] );
  200. // compute the slowly-varying normal and binormal vectors for each segment on the curve
  201. for ( i = 1; i <= segments; i ++ ) {
  202. normals[ i ] = normals[ i - 1 ].clone();
  203. binormals[ i ] = binormals[ i - 1 ].clone();
  204. vec.crossVectors( tangents[ i - 1 ], tangents[ i ] );
  205. if ( vec.length() > Number.EPSILON ) {
  206. vec.normalize();
  207. theta = Math.acos( _Math.clamp( tangents[ i - 1 ].dot( tangents[ i ] ), - 1, 1 ) ); // clamp for floating pt errors
  208. normals[ i ].applyMatrix4( mat.makeRotationAxis( vec, theta ) );
  209. }
  210. binormals[ i ].crossVectors( tangents[ i ], normals[ i ] );
  211. }
  212. // if the curve is closed, postprocess the vectors so the first and last normal vectors are the same
  213. if ( closed === true ) {
  214. theta = Math.acos( _Math.clamp( normals[ 0 ].dot( normals[ segments ] ), - 1, 1 ) );
  215. theta /= segments;
  216. if ( tangents[ 0 ].dot( vec.crossVectors( normals[ 0 ], normals[ segments ] ) ) > 0 ) {
  217. theta = - theta;
  218. }
  219. for ( i = 1; i <= segments; i ++ ) {
  220. // twist a little...
  221. normals[ i ].applyMatrix4( mat.makeRotationAxis( tangents[ i ], theta * i ) );
  222. binormals[ i ].crossVectors( tangents[ i ], normals[ i ] );
  223. }
  224. }
  225. return {
  226. tangents: tangents,
  227. normals: normals,
  228. binormals: binormals
  229. };
  230. }
  231. } );
  232. export { Curve };