Shape.js 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515
  1. /**
  2. * @author zz85 / http://www.lab4games.net/zz85/blog
  3. * Defines a 2d shape plane using paths.
  4. **/
  5. // STEP 1 Create a path.
  6. // STEP 2 Turn path into shape.
  7. // STEP 3 ExtrudeGeometry takes in Shape/Shapes
  8. // STEP 3a - Extract points from each shape, turn to vertices
  9. // STEP 3b - Triangulate each shape, add faces.
  10. THREE.Shape = function ( ) {
  11. THREE.Path.apply( this, arguments );
  12. this.holes = [];
  13. };
  14. THREE.Shape.prototype = new THREE.Path();
  15. THREE.Shape.prototype.constructor = THREE.Path;
  16. // Convenience method to return ExtrudeGeometry
  17. THREE.Shape.prototype.extrude = function ( options ) {
  18. var extruded = new THREE.ExtrudeGeometry( this, options );
  19. return extruded;
  20. };
  21. // Get points of holes
  22. THREE.Shape.prototype.getPointsHoles = function ( divisions ) {
  23. var i, il = this.holes.length, holesPts = [];
  24. for ( i = 0; i < il; i ++ ) {
  25. holesPts[ i ] = this.holes[ i ].getTransformedPoints( divisions, this.bends );
  26. }
  27. return holesPts;
  28. };
  29. // Get points of holes (spaced by regular distance)
  30. THREE.Shape.prototype.getSpacedPointsHoles = function ( divisions ) {
  31. var i, il = this.holes.length, holesPts = [];
  32. for ( i = 0; i < il; i ++ ) {
  33. holesPts[ i ] = this.holes[ i ].getSpacedPoints( divisions, this.bends );
  34. }
  35. return holesPts;
  36. };
  37. // Get points of shape and holes (keypoints based on segments parameter)
  38. THREE.Shape.prototype.extractAllPoints = function ( divisions ) {
  39. return {
  40. shape: this.getTransformedPoints( divisions ),
  41. holes: this.getPointsHoles( divisions )
  42. };
  43. };
  44. //
  45. // THREE.Shape.prototype.extractAllPointsWithBend = function ( divisions, bend ) {
  46. //
  47. // return {
  48. //
  49. // shape: this.transform( bend, divisions ),
  50. // holes: this.getPointsHoles( divisions, bend )
  51. //
  52. // };
  53. //
  54. // };
  55. // Get points of shape and holes (spaced by regular distance)
  56. THREE.Shape.prototype.extractAllSpacedPoints = function ( divisions ) {
  57. return {
  58. shape: this.getTransformedSpacedPoints( divisions ),
  59. holes: this.getSpacedPointsHoles( divisions )
  60. };
  61. };
  62. /**************************************************************
  63. * Utils
  64. **************************************************************/
  65. THREE.Shape.Utils = {
  66. /*
  67. contour - array of vector2 for contour
  68. holes - array of array of vector2
  69. */
  70. removeHoles: function ( contour, holes ) {
  71. var shape = contour.concat(); // work on this shape
  72. var allpoints = shape.concat();
  73. /* For each isolated shape, find the closest points and break to the hole to allow triangulation */
  74. var prevShapeVert, nextShapeVert,
  75. prevHoleVert, nextHoleVert,
  76. holeIndex, shapeIndex,
  77. shapeId, shapeGroup,
  78. h, h2,
  79. hole, shortest, d,
  80. p, pts1, pts2,
  81. tmpShape1, tmpShape2,
  82. tmpHole1, tmpHole2,
  83. verts = [];
  84. for ( h = 0; h < holes.length; h++ ) {
  85. hole = holes[ h ];
  86. /*
  87. shapeholes[ h ].concat(); // preserves original
  88. holes.push( hole );
  89. */
  90. allpoints = allpoints.concat( hole );
  91. shortest = Number.POSITIVE_INFINITY;
  92. // Find the shortest pair of pts between shape and hole
  93. // Note: Actually, I'm not sure now if we could optimize this to be faster than O(m*n)
  94. // Using distanceToSquared() intead of distanceTo() should speed a little
  95. // since running square roots operations are reduced.
  96. for ( h2 = 0; h2 < hole.length; h2 ++ ) {
  97. pts1 = hole[ h2 ];
  98. var dist = [];
  99. for ( p = 0; p < shape.length; p++ ) {
  100. pts2 = shape[ p ];
  101. d = pts1.distanceToSquared( pts2 );
  102. dist.push( d );
  103. if ( d < shortest ) {
  104. shortest = d;
  105. holeIndex = h2;
  106. shapeIndex = p;
  107. }
  108. }
  109. }
  110. //console.log("shortest", shortest, dist);
  111. prevShapeVert = ( shapeIndex - 1 ) >= 0 ? shapeIndex - 1 : shape.length - 1;
  112. prevHoleVert = ( holeIndex - 1 ) >= 0 ? holeIndex - 1 : hole.length - 1;
  113. var areaapts = [
  114. hole[ holeIndex ],
  115. shape[ shapeIndex ],
  116. shape[ prevShapeVert ]
  117. ];
  118. var areaa = THREE.FontUtils.Triangulate.area( areaapts );
  119. var areabpts = [
  120. hole[ holeIndex ],
  121. hole[ prevHoleVert ],
  122. shape[ shapeIndex ]
  123. ];
  124. var areab = THREE.FontUtils.Triangulate.area( areabpts );
  125. var shapeOffset = 1;
  126. var holeOffset = -1;
  127. var oldShapeIndex = shapeIndex, oldHoleIndex = holeIndex;
  128. shapeIndex += shapeOffset;
  129. holeIndex += holeOffset;
  130. if ( shapeIndex < 0 ) { shapeIndex += shape.length; }
  131. shapeIndex %= shape.length;
  132. if ( holeIndex < 0 ) { holeIndex += hole.length; }
  133. holeIndex %= hole.length;
  134. prevShapeVert = ( shapeIndex - 1 ) >= 0 ? shapeIndex - 1 : shape.length - 1;
  135. prevHoleVert = ( holeIndex - 1 ) >= 0 ? holeIndex - 1 : hole.length - 1;
  136. areaapts = [
  137. hole[ holeIndex ],
  138. shape[ shapeIndex ],
  139. shape[ prevShapeVert ]
  140. ];
  141. var areaa2 = THREE.FontUtils.Triangulate.area( areaapts );
  142. areabpts = [
  143. hole[ holeIndex ],
  144. hole[ prevHoleVert ],
  145. shape[ shapeIndex ]
  146. ];
  147. var areab2 = THREE.FontUtils.Triangulate.area( areabpts );
  148. //console.log(areaa,areab ,areaa2,areab2, ( areaa + areab ), ( areaa2 + areab2 ));
  149. if ( ( areaa + areab ) > ( areaa2 + areab2 ) ) {
  150. // In case areas are not correct.
  151. //console.log("USE THIS");
  152. shapeIndex = oldShapeIndex;
  153. holeIndex = oldHoleIndex ;
  154. if ( shapeIndex < 0 ) { shapeIndex += shape.length; }
  155. shapeIndex %= shape.length;
  156. if ( holeIndex < 0 ) { holeIndex += hole.length; }
  157. holeIndex %= hole.length;
  158. prevShapeVert = ( shapeIndex - 1 ) >= 0 ? shapeIndex - 1 : shape.length - 1;
  159. prevHoleVert = ( holeIndex - 1 ) >= 0 ? holeIndex - 1 : hole.length - 1;
  160. } else {
  161. //console.log("USE THAT ")
  162. }
  163. tmpShape1 = shape.slice( 0, shapeIndex );
  164. tmpShape2 = shape.slice( shapeIndex );
  165. tmpHole1 = hole.slice( holeIndex );
  166. tmpHole2 = hole.slice( 0, holeIndex );
  167. // Should check orders here again?
  168. var trianglea = [
  169. hole[ holeIndex ],
  170. shape[ shapeIndex ],
  171. shape[ prevShapeVert ]
  172. ];
  173. var triangleb = [
  174. hole[ holeIndex ] ,
  175. hole[ prevHoleVert ],
  176. shape[ shapeIndex ]
  177. ];
  178. verts.push( trianglea );
  179. verts.push( triangleb );
  180. shape = tmpShape1.concat( tmpHole1 ).concat( tmpHole2 ).concat( tmpShape2 );
  181. }
  182. return {
  183. shape:shape, /* shape with no holes */
  184. isolatedPts: verts, /* isolated faces */
  185. allpoints: allpoints
  186. }
  187. },
  188. triangulateShape: function ( contour, holes ) {
  189. var shapeWithoutHoles = THREE.Shape.Utils.removeHoles( contour, holes );
  190. var shape = shapeWithoutHoles.shape,
  191. allpoints = shapeWithoutHoles.allpoints,
  192. isolatedPts = shapeWithoutHoles.isolatedPts;
  193. var triangles = THREE.FontUtils.Triangulate( shape, false ); // True returns indices for points of spooled shape
  194. // To maintain reference to old shape, one must match coordinates, or offset the indices from original arrays. It's probably easier to do the first.
  195. //console.log( "triangles",triangles, triangles.length );
  196. //console.log( "allpoints",allpoints, allpoints.length );
  197. var i, il, f, face,
  198. key, index,
  199. allPointsMap = {},
  200. isolatedPointsMap = {};
  201. // prepare all points map
  202. for ( i = 0, il = allpoints.length; i < il; i ++ ) {
  203. key = allpoints[ i ].x + ":" + allpoints[ i ].y;
  204. if ( allPointsMap[ key ] !== undefined ) {
  205. console.log( "Duplicate point", key );
  206. }
  207. allPointsMap[ key ] = i;
  208. }
  209. // check all face vertices against all points map
  210. for ( i = 0, il = triangles.length; i < il; i ++ ) {
  211. face = triangles[ i ];
  212. for ( f = 0; f < 3; f ++ ) {
  213. key = face[ f ].x + ":" + face[ f ].y;
  214. index = allPointsMap[ key ];
  215. if ( index !== undefined ) {
  216. face[ f ] = index;
  217. }
  218. }
  219. }
  220. // check isolated points vertices against all points map
  221. for ( i = 0, il = isolatedPts.length; i < il; i ++ ) {
  222. face = isolatedPts[ i ];
  223. for ( f = 0; f < 3; f ++ ) {
  224. key = face[ f ].x + ":" + face[ f ].y;
  225. index = allPointsMap[ key ];
  226. if ( index !== undefined ) {
  227. face[ f ] = index;
  228. }
  229. }
  230. }
  231. return triangles.concat( isolatedPts );
  232. }, // end triangulate shapes
  233. /*
  234. triangulate2 : function( pts, holes ) {
  235. // For use with Poly2Tri.js
  236. var allpts = pts.concat();
  237. var shape = [];
  238. for (var p in pts) {
  239. shape.push(new js.poly2tri.Point(pts[p].x, pts[p].y));
  240. }
  241. var swctx = new js.poly2tri.SweepContext(shape);
  242. for (var h in holes) {
  243. var aHole = holes[h];
  244. var newHole = []
  245. for (i in aHole) {
  246. newHole.push(new js.poly2tri.Point(aHole[i].x, aHole[i].y));
  247. allpts.push(aHole[i]);
  248. }
  249. swctx.AddHole(newHole);
  250. }
  251. var find;
  252. var findIndexForPt = function (pt) {
  253. find = new THREE.Vector2(pt.x, pt.y);
  254. var p;
  255. for (p=0, pl = allpts.length; p<pl; p++) {
  256. if (allpts[p].equals(find)) return p;
  257. }
  258. return -1;
  259. };
  260. // triangulate
  261. js.poly2tri.sweep.Triangulate(swctx);
  262. var triangles = swctx.GetTriangles();
  263. var tr ;
  264. var facesPts = [];
  265. for (var t in triangles) {
  266. tr = triangles[t];
  267. facesPts.push([
  268. findIndexForPt(tr.GetPoint(0)),
  269. findIndexForPt(tr.GetPoint(1)),
  270. findIndexForPt(tr.GetPoint(2))
  271. ]);
  272. }
  273. // console.log(facesPts);
  274. // console.log("triangles", triangles.length, triangles);
  275. // Returns array of faces with 3 element each
  276. return facesPts;
  277. },
  278. */
  279. isClockWise: function ( pts ) {
  280. return THREE.FontUtils.Triangulate.area( pts ) < 0;
  281. },
  282. // Bezier Curves formulas obtained from
  283. // http://en.wikipedia.org/wiki/B%C3%A9zier_curve
  284. // Quad Bezier Functions
  285. b2p0: function ( t, p ) {
  286. var k = 1 - t;
  287. return k * k * p;
  288. },
  289. b2p1: function ( t, p ) {
  290. return 2 * ( 1 - t ) * t * p;
  291. },
  292. b2p2: function ( t, p ) {
  293. return t * t * p;
  294. },
  295. b2: function ( t, p0, p1, p2 ) {
  296. return this.b2p0( t, p0 ) + this.b2p1( t, p1 ) + this.b2p2( t, p2 );
  297. },
  298. // Cubic Bezier Functions
  299. b3p0: function ( t, p ) {
  300. var k = 1 - t;
  301. return k * k * k * p;
  302. },
  303. b3p1: function ( t, p ) {
  304. var k = 1 - t;
  305. return 3 * k * k * t * p;
  306. },
  307. b3p2: function ( t, p ) {
  308. var k = 1 - t;
  309. return 3 * k * t * t * p;
  310. },
  311. b3p3: function ( t, p ) {
  312. return t * t * t * p;
  313. },
  314. b3: function ( t, p0, p1, p2, p3 ) {
  315. return this.b3p0( t, p0 ) + this.b3p1( t, p1 ) + this.b3p2( t, p2 ) + this.b3p3( t, p3 );
  316. }
  317. };