Path.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785
  1. /**
  2. * @author zz85 / http://www.lab4games.net/zz85/blog
  3. * Creates free form 2d path using series of points, lines or curves.
  4. *
  5. **/
  6. THREE.Path = function ( points ) {
  7. this.actions = [];
  8. this.curves = [];
  9. if ( points ) {
  10. this.fromPoints( points );
  11. }
  12. };
  13. THREE.PathActions = {
  14. MOVE_TO: 'moveTo',
  15. LINE_TO: 'lineTo',
  16. QUADRATIC_CURVE_TO: 'quadraticCurveTo', // Bezier quadratic curve
  17. BEZIER_CURVE_TO: 'bezierCurveTo', // Bezier cubic curve
  18. CSPLINE_THRU: 'splineThru', // Catmull-rom spline
  19. ARC: 'arc' // Circle
  20. };
  21. // TODO Clean up PATH API
  22. // Create path using straight lines to connect all points
  23. // - vectors: array of Vector2
  24. THREE.Path.prototype.fromPoints = function( vectors ) {
  25. this.moveTo( vectors[ 0 ].x, vectors[ 0 ].y );
  26. var v, vlen = vectors.length;
  27. for ( v = 1; v < vlen; v++ ) {
  28. this.lineTo( vectors[ v ].x, vectors[ v ].y );
  29. };
  30. };
  31. // startPath() endPath()?
  32. THREE.Path.prototype.moveTo = function( x, y ) {
  33. var args = Array.prototype.slice.call( arguments );
  34. this.actions.push( { action: THREE.PathActions.MOVE_TO, args: args } );
  35. };
  36. THREE.Path.prototype.lineTo = function( x, y ) {
  37. var args = Array.prototype.slice.call( arguments );
  38. var lastargs = this.actions[ this.actions.length - 1 ].args;
  39. var x0 = lastargs[ lastargs.length - 2 ];
  40. var y0 = lastargs[ lastargs.length - 1 ];
  41. var curve = new THREE.LineCurve( x0, y0, x, y );
  42. this.curves.push( curve );
  43. this.actions.push( { action: THREE.PathActions.LINE_TO, args: args, curve: curve } );
  44. };
  45. THREE.Path.prototype.quadraticCurveTo = function( aCPx, aCPy, aX, aY ) {
  46. var args = Array.prototype.slice.call( arguments );
  47. var lastargs = this.actions[ this.actions.length - 1 ].args;
  48. var x0 = lastargs[ lastargs.length - 2 ];
  49. var y0 = lastargs[ lastargs.length - 1 ];
  50. var curve = new THREE.QuadraticBezierCurve( x0, y0, aCPx, aCPy, aX, aY );
  51. this.curves.push( curve );
  52. this.actions.push( { action: THREE.PathActions.QUADRATIC_CURVE_TO, args: args, curve: curve } );
  53. //console.log(curve, curve.getPoints(), curve.getSpacedPoints());
  54. //console.log(curve.getPointAt(0), curve.getPointAt(0),curve.getUtoTmapping(0), curve.getSpacedPoints());
  55. };
  56. THREE.Path.prototype.bezierCurveTo = function( aCP1x, aCP1y,
  57. aCP2x, aCP2y,
  58. aX, aY ) {
  59. var args = Array.prototype.slice.call( arguments );
  60. var lastargs = this.actions[ this.actions.length - 1 ].args;
  61. var x0 = lastargs[ lastargs.length - 2 ];
  62. var y0 = lastargs[ lastargs.length - 1 ];
  63. var curve = new THREE.CubicBezierCurve( x0, y0, aCP1x, aCP1y,
  64. aCP2x, aCP2y,
  65. aX, aY );
  66. this.curves.push( curve );
  67. this.actions.push( { action: THREE.PathActions.BEZIER_CURVE_TO, args: args, curve: curve } );
  68. };
  69. THREE.Path.prototype.splineThru = function( pts /*Array of Vector*/ ) {
  70. var args = Array.prototype.slice.call( arguments );
  71. var lastargs = this.actions[ this.actions.length - 1 ].args;
  72. var x0 = lastargs[ lastargs.length - 2 ];
  73. var y0 = lastargs[ lastargs.length - 1 ];
  74. var npts = [ new THREE.Vector2( x0, y0 ) ];
  75. npts = npts.concat( pts );
  76. var curve = new THREE.SplineCurve( npts );
  77. this.curves.push( curve );
  78. this.actions.push( { action: THREE.PathActions.CSPLINE_THRU, args: args, curve: curve } );
  79. //console.log(curve, curve.getPoints(), curve.getSpacedPoints());
  80. };
  81. // FUTURE: Change the API or follow canvas API?
  82. // TODO ARC ( x, y, x - radius, y - radius, startAngle, endAngle )
  83. THREE.Path.prototype.arc = function(aX, aY, aRadius,
  84. aStartAngle, aEndAngle, aClockwise ) {
  85. var args = Array.prototype.slice.call( arguments );
  86. var curve = new THREE.ArcCurve( aX, aY, aRadius,
  87. aStartAngle, aEndAngle, aClockwise );
  88. this.curves.push( curve );
  89. //console.log('arc', args);
  90. this.actions.push( { action: THREE.PathActions.ARC, args: args } );
  91. };
  92. /*
  93. // FUTURE ENHANCEMENTS
  94. example usage?
  95. Path.addExprFunc('sineCurveTo', sineCurveGetPtFunction)
  96. Path.sineCurveTo(x,y, amptitude);
  97. sineCurve.getPoint(t);
  98. return sine(disnt) * ampt
  99. // Create a new func eg. sin (theta) x
  100. THREE.Path.prototype.addExprFunc = function(exprName, func) {
  101. };
  102. */
  103. THREE.Path.prototype.getSpacedPoints = function( divisions ) {
  104. if ( !divisions ) divisions = 40;
  105. var pts = [];
  106. for ( var i = 0; i < divisions; i++ ) {
  107. pts.push( this.getPoint( i / divisions ) );
  108. //if(!this.getPoint(i/divisions)) throw "DIE";
  109. }
  110. //console.log(pts);
  111. return pts;
  112. };
  113. /* Return an array of vectors based on contour of the path */
  114. THREE.Path.prototype.getPoints = function( divisions ) {
  115. divisions = divisions || 12;
  116. var points = [];
  117. var i, il, item, action, args;
  118. var cpx, cpy, cpx2, cpy2, cpx1, cpy1, cpx0, cpy0,
  119. laste, j,
  120. t, tx, ty;
  121. for ( i = 0, il = this.actions.length; i < il; i++ ) {
  122. item = this.actions[ i ];
  123. action = item.action;
  124. args = item.args;
  125. switch( action ) {
  126. case THREE.PathActions.MOVE_TO:
  127. //points.push( new THREE.Vector2( args[ 0 ], args[ 1 ] ) );
  128. break;
  129. case THREE.PathActions.LINE_TO:
  130. points.push( new THREE.Vector2( args[ 0 ], args[ 1 ] ) );
  131. break;
  132. case THREE.PathActions.QUADRATIC_CURVE_TO:
  133. cpx = args[ 2 ];
  134. cpy = args[ 3 ];
  135. cpx1 = args[ 0 ];
  136. cpy1 = args[ 1 ];
  137. if ( points.length > 0 ) {
  138. laste = points[ points.length - 1 ];
  139. cpx0 = laste.x;
  140. cpy0 = laste.y;
  141. } else {
  142. laste = this.actions[ i - 1 ].args;
  143. cpx0 = laste[ laste.length - 2 ];
  144. cpy0 = laste[ laste.length - 1 ];
  145. }
  146. for ( j = 1; j <= divisions; j ++ ) {
  147. t = j / divisions;
  148. tx = THREE.FontUtils.b2( t, cpx0, cpx1, cpx );
  149. ty = THREE.FontUtils.b2( t, cpy0, cpy1, cpy );
  150. points.push( new THREE.Vector2( tx, ty ) );
  151. }
  152. break;
  153. case THREE.PathActions.BEZIER_CURVE_TO:
  154. cpx = args[ 4 ];
  155. cpy = args[ 5 ];
  156. cpx1 = args[ 0 ];
  157. cpy1 = args[ 1 ];
  158. cpx2 = args[ 2 ];
  159. cpy2 = args[ 3 ];
  160. if ( points.length > 0 ) {
  161. laste = points[ points.length - 1 ];
  162. cpx0 = laste.x;
  163. cpy0 = laste.y;
  164. } else {
  165. laste = this.actions[ i - 1 ].args;
  166. cpx0 = laste[ laste.length - 2 ];
  167. cpy0 = laste[ laste.length - 1 ];
  168. }
  169. for ( j = 1; j <= divisions; j ++ ) {
  170. t = j / divisions;
  171. tx = THREE.FontUtils.b3( t, cpx0, cpx1, cpx2, cpx );
  172. ty = THREE.FontUtils.b3( t, cpy0, cpy1, cpy2, cpy );
  173. points.push( new THREE.Vector2( tx, ty ) );
  174. }
  175. break;
  176. case THREE.PathActions.CSPLINE_THRU:
  177. laste = this.actions[ i - 1 ].args;
  178. var last = new THREE.Vector2( laste[ laste.length - 2 ], laste[ laste.length - 1 ] );
  179. var spts = [ last ];
  180. var n = divisions * args[ 0 ].length;
  181. spts = spts.concat( args[ 0 ] );
  182. var spline = new THREE.SplineCurve( spts );
  183. for ( j = 1; j <= n; j ++ ) {
  184. points.push( spline.getPointAt( j / n ) ) ;
  185. }
  186. break;
  187. case THREE.PathActions.ARC:
  188. laste = this.actions[ i - 1 ].args;
  189. var aX = args[ 0 ], aY = args[ 1 ],
  190. aRadius = args[ 2 ],
  191. aStartAngle = args[ 3 ], aEndAngle = args[ 4 ],
  192. aClockwise = !!args[ 5 ];
  193. var lastx = laste[ laste.length - 2 ],
  194. lasty = laste[ laste.length - 1 ];
  195. if ( laste.length == 0 ) {
  196. lastx = lasty = 0;
  197. }
  198. var deltaAngle = aEndAngle - aStartAngle;
  199. var angle;
  200. var tdivisions = divisions * 2;
  201. var t;
  202. for ( j = 1; j <= tdivisions; j ++ ) {
  203. t = j / tdivisions;
  204. if ( !aClockwise ) {
  205. t = 1 - t;
  206. }
  207. angle = aStartAngle + t * deltaAngle;
  208. tx = lastx + aX + aRadius * Math.cos( angle );
  209. ty = lasty + aY + aRadius * Math.sin( angle );
  210. //console.log('t', t, 'angle', angle, 'tx', tx, 'ty', ty);
  211. points.push( new THREE.Vector2( tx, ty ) );
  212. }
  213. //console.log(points);
  214. break;
  215. } // end switch
  216. }
  217. return points;
  218. };
  219. THREE.Path.prototype.getMinAndMax = function() {
  220. var points = this.getPoints();
  221. var maxX, maxY;
  222. var minX, minY;
  223. maxX = maxY = Number.NEGATIVE_INFINITY;
  224. minX = minY = Number.POSITIVE_INFINITY;
  225. var p, i, il;
  226. for ( i = 0, il = points.length; i < il; i ++ ) {
  227. p = points[ i ];
  228. if ( p.x > maxX ) maxX = p.x;
  229. else if ( p.x < minX ) minX = p.x;
  230. if ( p.y > maxY ) maxY = p.y;
  231. else if ( p.y < maxY ) minY = p.y;
  232. }
  233. // TODO Include CG or find mid-pt?
  234. return {
  235. minX: minX,
  236. minY: minY,
  237. maxX: maxX,
  238. maxY: maxY
  239. };
  240. };
  241. // To get accurate point with reference to
  242. // entire path distance at time t,
  243. // following has to be done
  244. // 1. Length of each sub path have to be known
  245. // 2. Locate and identify type of curve
  246. // 3. Get t for the curve
  247. // 4. Return curve.getPointAt(t')
  248. THREE.Path.prototype.getPoint = function( t ) {
  249. var d = t * this.getLength();
  250. var curveLengths = this.sums;
  251. var i = 0, diff, curve;
  252. // To think about boundaries points.
  253. while ( i < curveLengths.length ) {
  254. if ( curveLengths[ i ] >= d) {
  255. diff = curveLengths[ i ] - d;
  256. curve = this.curves[ i ];
  257. var u = 1 - diff / curve.getLength();
  258. return curve.getPointAt( u );
  259. break;
  260. }
  261. i++;
  262. }
  263. return null;
  264. // loop where sum != 0, sum > d , sum+1 <d
  265. };
  266. // Compute lengths and cache them
  267. THREE.Path.prototype.getLength = function() {
  268. // Loop all actions/path
  269. // Push sums into cached array
  270. var lengths = [], sums = 0;
  271. var i, il = this.curves.length;
  272. for ( i = 0; i < il ; i++ ) {
  273. sums += this.curves[ i ].getLength();
  274. lengths.push( sums );
  275. }
  276. this.sums = lengths;
  277. return sums;
  278. };
  279. // TODO: rewrite to use single Line object
  280. // createPathGeometry by SolarCoordinates
  281. /* Returns Object3D with line segments stored as children */
  282. THREE.Path.prototype.createPathGeometry = function( divisions, lineMaterial ) {
  283. var pts = this.getPoints( divisions );
  284. var segment, pathGeometry = new THREE.Object3D;
  285. if ( !lineMaterial ) lineMaterial = new THREE.LineBasicMaterial( { color: 0x000000, opacity: 0.7 } );
  286. for( var i = 1; i < pts.length; i++ ) {
  287. var pathSegment = new THREE.Geometry();
  288. pathSegment.vertices.push( new THREE.Vertex( new THREE.Vector3( pts[i-1].x, pts[i-1].y, 0 ) ) );
  289. pathSegment.vertices.push( new THREE.Vertex( new THREE.Vector3( pts[i].x, pts[i].y, 0) ) );
  290. segment = new THREE.Line( pathSegment , lineMaterial );
  291. pathGeometry.addChild(segment);
  292. }
  293. return pathGeometry;
  294. };
  295. // ALL THINGS BELOW TO BE REFACTORED
  296. // QN: Transform final pts or transform ACTIONS or add transform filters?
  297. // FUTURE refactor path = an array of lines -> straight, bezier, splines, arc, funcexpr lines
  298. // Read http://www.planetclegg.com/projects/WarpingTextToSplines.html
  299. THREE.Path.prototype.transform = function( path ) {
  300. path = new THREE.Path();
  301. path.moveTo( 0, 0 );
  302. path.quadraticCurveTo( 100, 20, 140, 80 );
  303. console.log( path.cacheArcLengths() );
  304. var thisBounds = this.getMinAndMax();
  305. var oldPts = this.getPoints();
  306. var i, il, p, oldX, oldY, xNorm;
  307. for ( i = 0, il = oldPts.length; i < il; i ++ ) {
  308. p = oldPts[ i ];
  309. oldX = p.x;
  310. oldY = p.y;
  311. var xNorm = oldX/ thisBounds.maxX;
  312. // If using actual distance, for length > path, requires line extrusions
  313. //xNorm = path.getUtoTmapping(xNorm, oldX); // 3 styles. 1) wrap stretched. 2) wrap stretch by arc length 3) warp by actual distance
  314. var pathPt = path.getPoint( xNorm );
  315. var normal = path.getNormalVector( xNorm ).multiplyScalar( oldY );
  316. p.x = pathPt.x + normal.x;
  317. p.y = pathPt.y + normal.y;
  318. //p.x = a * oldX + b * oldY + c;
  319. //p.y = d * oldY + e * oldX + f;
  320. }
  321. return oldPts;
  322. };
  323. // Read http://www.tinaja.com/glib/nonlingr.pdf
  324. // nonlinear transforms
  325. THREE.Path.prototype.nltransform = function( a, b, c, d, e, f ) {
  326. // a - horizontal size
  327. // b - lean
  328. // c - x offset
  329. // d - vertical size
  330. // e - climb
  331. // f - y offset
  332. var oldPts = this.getPoints();
  333. var i, il, p, oldX, oldY;
  334. for ( i = 0, il = oldPts.length; i < il; i ++ ) {
  335. p = oldPts[i];
  336. oldX = p.x;
  337. oldY = p.y;
  338. p.x = a * oldX + b * oldY + c;
  339. p.y = d * oldY + e * oldX + f;
  340. }
  341. return oldPts;
  342. };
  343. // FUTURE Export JSON Format
  344. /* Draws this path onto a 2d canvas easily */
  345. THREE.Path.prototype.debug = function( canvas ) {
  346. var bounds = this.getMinAndMax();
  347. if ( !canvas ) {
  348. canvas = document.createElement( "canvas" );
  349. canvas.setAttribute( 'width', bounds.maxX + 100 );
  350. canvas.setAttribute( 'height', bounds.maxY + 100 );
  351. document.body.appendChild( canvas );
  352. }
  353. var ctx = canvas.getContext( "2d" );
  354. ctx.fillStyle = "white";
  355. ctx.fillRect( 0, 0, canvas.width, canvas.height );
  356. ctx.strokeStyle = "black";
  357. ctx.beginPath();
  358. var i, il, item, action, args;
  359. // Debug Path
  360. for ( i = 0, il = this.actions.length; i < il; i ++ ) {
  361. item = this.actions[ i ];
  362. args = item.args;
  363. action = item.action;
  364. // Short hand for now
  365. if ( action != THREE.PathActions.CSPLINE_THRU ) {
  366. ctx[ action ].apply( ctx, args );
  367. }
  368. /*
  369. switch ( action ) {
  370. case THREE.PathActions.MOVE_TO:
  371. ctx[ action ]( args[ 0 ], args[ 1 ] );
  372. break;
  373. case THREE.PathActions.LINE_TO:
  374. ctx[ action ]( args[ 0 ], args[ 1 ] );
  375. break;
  376. case THREE.PathActions.QUADRATIC_CURVE_TO:
  377. ctx[ action ]( args[ 0 ], args[ 1 ], args[ 2 ], args[ 3 ] );
  378. break;
  379. case THREE.PathActions.CUBIC_CURVE_TO:
  380. ctx[ action ]( args[ 0 ], args[ 1 ], args[ 2 ], args[ 3 ], args[ 4 ], args[ 5 ] );
  381. break;
  382. }
  383. */
  384. }
  385. ctx.stroke();
  386. ctx.closePath();
  387. // Debug Points
  388. ctx.strokeStyle = "red";
  389. /* TO CLEAN UP */
  390. //var p, points = this.getPoints();
  391. var theta = -90 /180 * Math.PI;
  392. var p, points = this.transform( 0.866, - 0.866,0, 0.500 , 0.50,-50 );
  393. //0.866, - 0.866,0, 0.500 , 0.50,-50
  394. // Math.cos(theta),Math.sin(theta),100,
  395. // Math.cos(theta),-Math.sin(theta),-50
  396. // translate, scale, rotation
  397. // a - horizontal size
  398. // b - lean
  399. // c - x offset
  400. // d - vertical size
  401. // e - climb
  402. // f - y offset
  403. // 1,0,0,
  404. // -1,0,100
  405. for ( i = 0, il = points.length; i < il; i ++ ) {
  406. p = points[ i ];
  407. ctx.beginPath();
  408. ctx.arc( p.x, p.y, 1.5, 0, Math.PI * 2, false );
  409. ctx.stroke();
  410. ctx.closePath();
  411. }
  412. };
  413. // Breaks path into shapes
  414. THREE.Path.prototype.toShapes = function() {
  415. var i, il, item, action, args;
  416. var subPaths = [], lastPath = new THREE.Path();
  417. for ( i = 0, il = this.actions.length; i < il; i ++ ) {
  418. item = this.actions[ i ];
  419. args = item.args;
  420. action = item.action;
  421. if (action==THREE.PathActions.MOVE_TO) {
  422. if (lastPath.actions.length!=0) {
  423. subPaths.push(lastPath);
  424. lastPath = new THREE.Path();
  425. }
  426. }
  427. lastPath[action].apply( lastPath, args);
  428. }
  429. if (lastPath.actions.length!=0) {
  430. subPaths.push(lastPath);
  431. }
  432. console.log(subPaths);
  433. var holesFirst = !THREE.Shape.Utils.isClockWise(subPaths[0].getPoints());
  434. var tmpShape, shapes = [];
  435. var tmpPath;
  436. console.log("Holes first", holesFirst);
  437. if (holesFirst) {
  438. tmpShape = new THREE.Shape();
  439. for ( i=0, il = subPaths.length; i<il; i++) {
  440. tmpPath = subPaths[i];
  441. if (THREE.Shape.Utils.isClockWise(tmpPath.getPoints())) {
  442. tmpShape.actions = tmpPath.actions;
  443. tmpShape.curves = tmpPath.curves;
  444. shapes.push(tmpShape);
  445. tmpShape = new THREE.Shape();
  446. console.log('cw', i);
  447. } else {
  448. tmpShape.holes.push(tmpPath);
  449. console.log('ccw', i);
  450. }
  451. }
  452. } else {
  453. // Shapes first
  454. for ( i=0, il = subPaths.length; i<il; i++) {
  455. tmpPath = subPaths[i];
  456. if (THREE.Shape.Utils.isClockWise(tmpPath.getPoints())) {
  457. if (tmpShape) shapes.push(tmpShape);
  458. tmpShape = new THREE.Shape();
  459. tmpShape.actions = tmpPath.actions;
  460. tmpShape.curves = tmpPath.curves;
  461. } else {
  462. tmpShape.holes.push(tmpPath);
  463. }
  464. }
  465. shapes.push(tmpShape);
  466. }
  467. console.log("shape", shapes);
  468. return shapes;
  469. };
粤ICP备19079148号