Curve.js 8.1 KB

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