SVGLoader.js 25 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033
  1. /**
  2. * @author mrdoob / http://mrdoob.com/
  3. * @author zz85 / http://joshuakoo.com/
  4. * @author yomboprime / https://yombo.org
  5. */
  6. THREE.SVGLoader = function ( manager ) {
  7. this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager;
  8. this.nodeMap = new Map();
  9. this.rootNode = null;
  10. };
  11. THREE.SVGLoader.prototype = {
  12. constructor: THREE.SVGLoader,
  13. load: function ( url, onLoad, onProgress, onError ) {
  14. var scope = this;
  15. var loader = new THREE.FileLoader( scope.manager );
  16. loader.setPath( scope.path );
  17. loader.load( url, function ( text ) {
  18. onLoad( scope.parse( text ) );
  19. }, onProgress, onError );
  20. },
  21. setPath: function ( value ) {
  22. this.path = value;
  23. return this;
  24. },
  25. parse: function ( text ) {
  26. function parseNode( node, style ) {
  27. if ( node.nodeType !== 1 ) return;
  28. var transform = getNodeTransform( node );
  29. var path = null;
  30. switch ( node.nodeName ) {
  31. case 'svg':
  32. break;
  33. case 'g':
  34. style = parseStyle( node, style );
  35. break;
  36. case 'path':
  37. style = parseStyle( node, style );
  38. if ( node.hasAttribute( 'd' ) && isVisible( style ) ) path = parsePathNode( node, style );
  39. break;
  40. case 'rect':
  41. style = parseStyle( node, style );
  42. if ( isVisible( style ) ) path = parseRectNode( node, style );
  43. break;
  44. case 'polygon':
  45. style = parseStyle( node, style );
  46. if ( isVisible( style ) ) path = parsePolygonNode( node, style );
  47. break;
  48. case 'polyline':
  49. style = parseStyle( node, style );
  50. if ( isVisible( style ) ) path = parsePolylineNode( node, style );
  51. break;
  52. case 'circle':
  53. style = parseStyle( node, style );
  54. if ( isVisible( style ) ) path = parseCircleNode( node, style );
  55. break;
  56. case 'ellipse':
  57. style = parseStyle( node, style );
  58. if ( isVisible( style ) ) path = parseEllipseNode( node, style );
  59. break;
  60. case 'line':
  61. style = parseStyle( node, style );
  62. if ( isVisible( style ) ) path = parseLineNode( node, style );
  63. break;
  64. default:
  65. console.log( node );
  66. }
  67. if ( path ) {
  68. transformPath( path, currentTransform );
  69. paths.push( path );
  70. path.userData = { node: node, style: style };
  71. }
  72. var nodes = node.childNodes;
  73. for ( var i = 0; i < nodes.length; i ++ ) {
  74. parseNode( nodes[ i ], style );
  75. }
  76. if ( transform ) {
  77. currentTransform.copy( transformStack.pop() );
  78. }
  79. }
  80. function parsePathNode( node, style ) {
  81. var path = new THREE.ShapePath();
  82. path.color.setStyle( style.fill );
  83. var point = new THREE.Vector2();
  84. var control = new THREE.Vector2();
  85. var firstPoint = new THREE.Vector2();
  86. var isFirstPoint = true;
  87. var doSetFirstPoint = false;
  88. var d = node.getAttribute( 'd' );
  89. // console.log( d );
  90. var commands = d.match( /[a-df-z][^a-df-z]*/ig );
  91. for ( var i = 0, l = commands.length; i < l; i ++ ) {
  92. var command = commands[ i ];
  93. var type = command.charAt( 0 );
  94. var data = command.substr( 1 ).trim();
  95. if ( isFirstPoint === true ) {
  96. doSetFirstPoint = true;
  97. isFirstPoint = false;
  98. }
  99. switch ( type ) {
  100. case 'M':
  101. var numbers = parseFloats( data );
  102. for ( var j = 0, jl = numbers.length; j < jl; j += 2 ) {
  103. point.x = numbers[ j + 0 ];
  104. point.y = numbers[ j + 1 ];
  105. control.x = point.x;
  106. control.y = point.y;
  107. if ( j === 0 ) {
  108. path.moveTo( point.x, point.y );
  109. } else {
  110. path.lineTo( point.x, point.y );
  111. }
  112. if ( j === 0 && doSetFirstPoint === true ) firstPoint.copy( point );
  113. }
  114. break;
  115. case 'H':
  116. var numbers = parseFloats( data );
  117. for ( var j = 0, jl = numbers.length; j < jl; j ++ ) {
  118. point.x = numbers[ j ];
  119. control.x = point.x;
  120. control.y = point.y;
  121. path.lineTo( point.x, point.y );
  122. if ( j === 0 && doSetFirstPoint === true ) firstPoint.copy( point );
  123. }
  124. break;
  125. case 'V':
  126. var numbers = parseFloats( data );
  127. for ( var j = 0, jl = numbers.length; j < jl; j ++ ) {
  128. point.y = numbers[ j ];
  129. control.x = point.x;
  130. control.y = point.y;
  131. path.lineTo( point.x, point.y );
  132. if ( j === 0 && doSetFirstPoint === true ) firstPoint.copy( point );
  133. }
  134. break;
  135. case 'L':
  136. var numbers = parseFloats( data );
  137. for ( var j = 0, jl = numbers.length; j < jl; j += 2 ) {
  138. point.x = numbers[ j + 0 ];
  139. point.y = numbers[ j + 1 ];
  140. control.x = point.x;
  141. control.y = point.y;
  142. path.lineTo( point.x, point.y );
  143. if ( j === 0 && doSetFirstPoint === true ) firstPoint.copy( point );
  144. }
  145. break;
  146. case 'C':
  147. var numbers = parseFloats( data );
  148. for ( var j = 0, jl = numbers.length; j < jl; j += 6 ) {
  149. path.bezierCurveTo(
  150. numbers[ j + 0 ],
  151. numbers[ j + 1 ],
  152. numbers[ j + 2 ],
  153. numbers[ j + 3 ],
  154. numbers[ j + 4 ],
  155. numbers[ j + 5 ]
  156. );
  157. control.x = numbers[ j + 2 ];
  158. control.y = numbers[ j + 3 ];
  159. point.x = numbers[ j + 4 ];
  160. point.y = numbers[ j + 5 ];
  161. if ( j === 0 && doSetFirstPoint === true ) firstPoint.copy( point );
  162. }
  163. break;
  164. case 'S':
  165. var numbers = parseFloats( data );
  166. for ( var j = 0, jl = numbers.length; j < jl; j += 4 ) {
  167. path.bezierCurveTo(
  168. getReflection( point.x, control.x ),
  169. getReflection( point.y, control.y ),
  170. numbers[ j + 0 ],
  171. numbers[ j + 1 ],
  172. numbers[ j + 2 ],
  173. numbers[ j + 3 ]
  174. );
  175. control.x = numbers[ j + 0 ];
  176. control.y = numbers[ j + 1 ];
  177. point.x = numbers[ j + 2 ];
  178. point.y = numbers[ j + 3 ];
  179. if ( j === 0 && doSetFirstPoint === true ) firstPoint.copy( point );
  180. }
  181. break;
  182. case 'Q':
  183. var numbers = parseFloats( data );
  184. for ( var j = 0, jl = numbers.length; j < jl; j += 4 ) {
  185. path.quadraticCurveTo(
  186. numbers[ j + 0 ],
  187. numbers[ j + 1 ],
  188. numbers[ j + 2 ],
  189. numbers[ j + 3 ]
  190. );
  191. control.x = numbers[ j + 0 ];
  192. control.y = numbers[ j + 1 ];
  193. point.x = numbers[ j + 2 ];
  194. point.y = numbers[ j + 3 ];
  195. if ( j === 0 && doSetFirstPoint === true ) firstPoint.copy( point );
  196. }
  197. break;
  198. case 'T':
  199. var numbers = parseFloats( data );
  200. for ( var j = 0, jl = numbers.length; j < jl; j += 2 ) {
  201. var rx = getReflection( point.x, control.x );
  202. var ry = getReflection( point.y, control.y );
  203. path.quadraticCurveTo(
  204. rx,
  205. ry,
  206. numbers[ j + 0 ],
  207. numbers[ j + 1 ]
  208. );
  209. control.x = rx;
  210. control.y = ry;
  211. point.x = numbers[ j + 0 ];
  212. point.y = numbers[ j + 1 ];
  213. if ( j === 0 && doSetFirstPoint === true ) firstPoint.copy( point );
  214. }
  215. break;
  216. case 'A':
  217. var numbers = parseFloats( data );
  218. for ( var j = 0, jl = numbers.length; j < jl; j += 7 ) {
  219. var start = point.clone();
  220. point.x = numbers[ j + 5 ];
  221. point.y = numbers[ j + 6 ];
  222. control.x = point.x;
  223. control.y = point.y;
  224. parseArcCommand(
  225. path, numbers[ j ], numbers[ j + 1 ], numbers[ j + 2 ], numbers[ j + 3 ], numbers[ j + 4 ], start, point
  226. );
  227. if ( j === 0 && doSetFirstPoint === true ) firstPoint.copy( point );
  228. }
  229. break;
  230. //
  231. case 'm':
  232. var numbers = parseFloats( data );
  233. for ( var j = 0, jl = numbers.length; j < jl; j += 2 ) {
  234. point.x += numbers[ j + 0 ];
  235. point.y += numbers[ j + 1 ];
  236. control.x = point.x;
  237. control.y = point.y;
  238. if ( j === 0 ) {
  239. path.moveTo( point.x, point.y );
  240. } else {
  241. path.lineTo( point.x, point.y );
  242. }
  243. if ( j === 0 && doSetFirstPoint === true ) firstPoint.copy( point );
  244. }
  245. break;
  246. case 'h':
  247. var numbers = parseFloats( data );
  248. for ( var j = 0, jl = numbers.length; j < jl; j ++ ) {
  249. point.x += numbers[ j ];
  250. control.x = point.x;
  251. control.y = point.y;
  252. path.lineTo( point.x, point.y );
  253. if ( j === 0 && doSetFirstPoint === true ) firstPoint.copy( point );
  254. }
  255. break;
  256. case 'v':
  257. var numbers = parseFloats( data );
  258. for ( var j = 0, jl = numbers.length; j < jl; j ++ ) {
  259. point.y += numbers[ j ];
  260. control.x = point.x;
  261. control.y = point.y;
  262. path.lineTo( point.x, point.y );
  263. if ( j === 0 && doSetFirstPoint === true ) firstPoint.copy( point );
  264. }
  265. break;
  266. case 'l':
  267. var numbers = parseFloats( data );
  268. for ( var j = 0, jl = numbers.length; j < jl; j += 2 ) {
  269. point.x += numbers[ j + 0 ];
  270. point.y += numbers[ j + 1 ];
  271. control.x = point.x;
  272. control.y = point.y;
  273. path.lineTo( point.x, point.y );
  274. if ( j === 0 && doSetFirstPoint === true ) firstPoint.copy( point );
  275. }
  276. break;
  277. case 'c':
  278. var numbers = parseFloats( data );
  279. for ( var j = 0, jl = numbers.length; j < jl; j += 6 ) {
  280. path.bezierCurveTo(
  281. point.x + numbers[ j + 0 ],
  282. point.y + numbers[ j + 1 ],
  283. point.x + numbers[ j + 2 ],
  284. point.y + numbers[ j + 3 ],
  285. point.x + numbers[ j + 4 ],
  286. point.y + numbers[ j + 5 ]
  287. );
  288. control.x = point.x + numbers[ j + 2 ];
  289. control.y = point.y + numbers[ j + 3 ];
  290. point.x += numbers[ j + 4 ];
  291. point.y += numbers[ j + 5 ];
  292. if ( j === 0 && doSetFirstPoint === true ) firstPoint.copy( point );
  293. }
  294. break;
  295. case 's':
  296. var numbers = parseFloats( data );
  297. for ( var j = 0, jl = numbers.length; j < jl; j += 4 ) {
  298. path.bezierCurveTo(
  299. getReflection( point.x, control.x ),
  300. getReflection( point.y, control.y ),
  301. point.x + numbers[ j + 0 ],
  302. point.y + numbers[ j + 1 ],
  303. point.x + numbers[ j + 2 ],
  304. point.y + numbers[ j + 3 ]
  305. );
  306. control.x = point.x + numbers[ j + 0 ];
  307. control.y = point.y + numbers[ j + 1 ];
  308. point.x += numbers[ j + 2 ];
  309. point.y += numbers[ j + 3 ];
  310. if ( j === 0 && doSetFirstPoint === true ) firstPoint.copy( point );
  311. }
  312. break;
  313. case 'q':
  314. var numbers = parseFloats( data );
  315. for ( var j = 0, jl = numbers.length; j < jl; j += 4 ) {
  316. path.quadraticCurveTo(
  317. point.x + numbers[ j + 0 ],
  318. point.y + numbers[ j + 1 ],
  319. point.x + numbers[ j + 2 ],
  320. point.y + numbers[ j + 3 ]
  321. );
  322. control.x = point.x + numbers[ j + 0 ];
  323. control.y = point.y + numbers[ j + 1 ];
  324. point.x += numbers[ j + 2 ];
  325. point.y += numbers[ j + 3 ];
  326. if ( j === 0 && doSetFirstPoint === true ) firstPoint.copy( point );
  327. }
  328. break;
  329. case 't':
  330. var numbers = parseFloats( data );
  331. for ( var j = 0, jl = numbers.length; j < jl; j += 2 ) {
  332. var rx = getReflection( point.x, control.x );
  333. var ry = getReflection( point.y, control.y );
  334. path.quadraticCurveTo(
  335. rx,
  336. ry,
  337. point.x + numbers[ j + 0 ],
  338. point.y + numbers[ j + 1 ]
  339. );
  340. control.x = rx;
  341. control.y = ry;
  342. point.x = point.x + numbers[ j + 0 ];
  343. point.y = point.y + numbers[ j + 1 ];
  344. if ( j === 0 && doSetFirstPoint === true ) firstPoint.copy( point );
  345. }
  346. break;
  347. case 'a':
  348. var numbers = parseFloats( data );
  349. for ( var j = 0, jl = numbers.length; j < jl; j += 7 ) {
  350. var start = point.clone();
  351. point.x += numbers[ j + 5 ];
  352. point.y += numbers[ j + 6 ];
  353. control.x = point.x;
  354. control.y = point.y;
  355. parseArcCommand(
  356. path, numbers[ j ], numbers[ j + 1 ], numbers[ j + 2 ], numbers[ j + 3 ], numbers[ j + 4 ], start, point
  357. );
  358. if ( j === 0 && doSetFirstPoint === true ) firstPoint.copy( point );
  359. }
  360. break;
  361. //
  362. case 'Z':
  363. case 'z':
  364. path.currentPath.autoClose = true;
  365. if ( path.currentPath.curves.length > 0 ) {
  366. // Reset point to beginning of Path
  367. point.copy( firstPoint );
  368. path.currentPath.currentPoint.copy( point );
  369. isFirstPoint = true;
  370. }
  371. break;
  372. default:
  373. console.warn( command );
  374. }
  375. // console.log( type, parseFloats( data ), parseFloats( data ).length )
  376. doSetFirstPoint = false;
  377. }
  378. return path;
  379. }
  380. /**
  381. * https://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes
  382. * https://mortoray.com/2017/02/16/rendering-an-svg-elliptical-arc-as-bezier-curves/ Appendix: Endpoint to center arc conversion
  383. * From
  384. * rx ry x-axis-rotation large-arc-flag sweep-flag x y
  385. * To
  386. * aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation
  387. */
  388. function parseArcCommand( path, rx, ry, x_axis_rotation, large_arc_flag, sweep_flag, start, end ) {
  389. x_axis_rotation = x_axis_rotation * Math.PI / 180;
  390. // Ensure radii are positive
  391. rx = Math.abs( rx );
  392. ry = Math.abs( ry );
  393. // Compute (x1′, y1′)
  394. var dx2 = ( start.x - end.x ) / 2.0;
  395. var dy2 = ( start.y - end.y ) / 2.0;
  396. var x1p = Math.cos( x_axis_rotation ) * dx2 + Math.sin( x_axis_rotation ) * dy2;
  397. var y1p = - Math.sin( x_axis_rotation ) * dx2 + Math.cos( x_axis_rotation ) * dy2;
  398. // Compute (cx′, cy′)
  399. var rxs = rx * rx;
  400. var rys = ry * ry;
  401. var x1ps = x1p * x1p;
  402. var y1ps = y1p * y1p;
  403. // Ensure radii are large enough
  404. var cr = x1ps / rxs + y1ps / rys;
  405. if ( cr > 1 ) {
  406. // scale up rx,ry equally so cr == 1
  407. var s = Math.sqrt( cr );
  408. rx = s * rx;
  409. ry = s * ry;
  410. rxs = rx * rx;
  411. rys = ry * ry;
  412. }
  413. var dq = ( rxs * y1ps + rys * x1ps );
  414. var pq = ( rxs * rys - dq ) / dq;
  415. var q = Math.sqrt( Math.max( 0, pq ) );
  416. if ( large_arc_flag === sweep_flag ) q = - q;
  417. var cxp = q * rx * y1p / ry;
  418. var cyp = - q * ry * x1p / rx;
  419. // Step 3: Compute (cx, cy) from (cx′, cy′)
  420. var cx = Math.cos( x_axis_rotation ) * cxp - Math.sin( x_axis_rotation ) * cyp + ( start.x + end.x ) / 2;
  421. var cy = Math.sin( x_axis_rotation ) * cxp + Math.cos( x_axis_rotation ) * cyp + ( start.y + end.y ) / 2;
  422. // Step 4: Compute θ1 and Δθ
  423. var theta = svgAngle( 1, 0, ( x1p - cxp ) / rx, ( y1p - cyp ) / ry );
  424. var delta = svgAngle( ( x1p - cxp ) / rx, ( y1p - cyp ) / ry, ( - x1p - cxp ) / rx, ( - y1p - cyp ) / ry ) % ( Math.PI * 2 );
  425. path.currentPath.absellipse( cx, cy, rx, ry, theta, theta + delta, sweep_flag === 0, x_axis_rotation );
  426. }
  427. function svgAngle( ux, uy, vx, vy ) {
  428. var dot = ux * vx + uy * vy;
  429. var len = Math.sqrt( ux * ux + uy * uy ) * Math.sqrt( vx * vx + vy * vy );
  430. var ang = Math.acos( Math.max( -1, Math.min( 1, dot / len ) ) ); // floating point precision, slightly over values appear
  431. if ( ( ux * vy - uy * vx ) < 0 ) ang = - ang;
  432. return ang;
  433. }
  434. /*
  435. * According to https://www.w3.org/TR/SVG/shapes.html#RectElementRXAttribute
  436. * rounded corner should be rendered to elliptical arc, but bezier curve does the job well enough
  437. */
  438. function parseRectNode( node, style ) {
  439. var x = parseFloat( node.getAttribute( 'x' ) || 0 );
  440. var y = parseFloat( node.getAttribute( 'y' ) || 0 );
  441. var rx = parseFloat( node.getAttribute( 'rx' ) || 0 );
  442. var ry = parseFloat( node.getAttribute( 'ry' ) || 0 );
  443. var w = parseFloat( node.getAttribute( 'width' ) );
  444. var h = parseFloat( node.getAttribute( 'height' ) );
  445. var path = new THREE.ShapePath();
  446. path.color.setStyle( style.fill );
  447. path.moveTo( x + 2 * rx, y );
  448. path.lineTo( x + w - 2 * rx, y );
  449. if ( rx !== 0 || ry !== 0 ) path.bezierCurveTo( x + w, y, x + w, y, x + w, y + 2 * ry );
  450. path.lineTo( x + w, y + h - 2 * ry );
  451. if ( rx !== 0 || ry !== 0 ) path.bezierCurveTo( x + w, y + h, x + w, y + h, x + w - 2 * rx, y + h );
  452. path.lineTo( x + 2 * rx, y + h );
  453. if ( rx !== 0 || ry !== 0 ) {
  454. path.bezierCurveTo( x, y + h, x, y + h, x, y + h - 2 * ry );
  455. }
  456. path.lineTo( x, y + 2 * ry );
  457. if ( rx !== 0 || ry !== 0 ) {
  458. path.bezierCurveTo( x, y, x, y, x + 2 * rx, y );
  459. }
  460. return path;
  461. }
  462. function parsePolygonNode( node, style ) {
  463. function iterator( match, a, b ) {
  464. var x = parseFloat( a );
  465. var y = parseFloat( b );
  466. if ( index === 0 ) {
  467. path.moveTo( x, y );
  468. } else {
  469. path.lineTo( x, y );
  470. }
  471. index ++;
  472. }
  473. var regex = /(-?[\d\.?]+)[,|\s](-?[\d\.?]+)/g;
  474. var path = new THREE.ShapePath();
  475. path.color.setStyle( style.fill );
  476. var index = 0;
  477. node.getAttribute( 'points' ).replace(regex, iterator);
  478. path.currentPath.autoClose = true;
  479. return path;
  480. }
  481. function parsePolylineNode( node, style ) {
  482. function iterator( match, a, b ) {
  483. var x = parseFloat( a );
  484. var y = parseFloat( b );
  485. if ( index === 0 ) {
  486. path.moveTo( x, y );
  487. } else {
  488. path.lineTo( x, y );
  489. }
  490. index ++;
  491. }
  492. var regex = /(-?[\d\.?]+)[,|\s](-?[\d\.?]+)/g;
  493. var path = new THREE.ShapePath();
  494. path.color.setStyle( style.fill );
  495. var index = 0;
  496. node.getAttribute( 'points' ).replace(regex, iterator);
  497. path.currentPath.autoClose = false;
  498. return path;
  499. }
  500. function parseCircleNode( node, style ) {
  501. var x = parseFloat( node.getAttribute( 'cx' ) );
  502. var y = parseFloat( node.getAttribute( 'cy' ) );
  503. var r = parseFloat( node.getAttribute( 'r' ) );
  504. var subpath = new THREE.Path();
  505. subpath.absarc( x, y, r, 0, Math.PI * 2 );
  506. var path = new THREE.ShapePath();
  507. path.color.setStyle( style.fill );
  508. path.subPaths.push( subpath );
  509. return path;
  510. }
  511. function parseEllipseNode( node, style ) {
  512. var x = parseFloat( node.getAttribute( 'cx' ) );
  513. var y = parseFloat( node.getAttribute( 'cy' ) );
  514. var rx = parseFloat( node.getAttribute( 'rx' ) );
  515. var ry = parseFloat( node.getAttribute( 'ry' ) );
  516. var subpath = new THREE.Path();
  517. subpath.absellipse( x, y, rx, ry, 0, Math.PI * 2 );
  518. var path = new THREE.ShapePath();
  519. path.color.setStyle( style.fill );
  520. path.subPaths.push( subpath );
  521. return path;
  522. }
  523. function parseLineNode( node, style ) {
  524. var x1 = parseFloat( node.getAttribute( 'x1' ) );
  525. var y1 = parseFloat( node.getAttribute( 'y1' ) );
  526. var x2 = parseFloat( node.getAttribute( 'x2' ) );
  527. var y2 = parseFloat( node.getAttribute( 'y2' ) );
  528. var path = new THREE.ShapePath();
  529. path.moveTo( x1, y1 );
  530. path.lineTo( x2, y2 );
  531. path.currentPath.autoClose = false;
  532. return path;
  533. }
  534. //
  535. function parseStyle( node, style ) {
  536. style = Object.assign( {}, style ); // clone style
  537. if ( node.hasAttribute( 'fill' ) ) style.fill = node.getAttribute( 'fill' );
  538. if ( node.style.fill !== '' ) style.fill = node.style.fill;
  539. if ( node.hasAttribute( 'fill-opacity' ) ) style.fillOpacity = node.getAttribute( 'fill-opacity' );
  540. if ( node.style.fillOpacity !== '' ) style.fillOpacity = node.style.fillOpacity;
  541. return style;
  542. }
  543. function isVisible( style ) {
  544. return style.fill !== 'none' && style.fill !== 'transparent';
  545. }
  546. // http://www.w3.org/TR/SVG11/implnote.html#PathElementImplementationNotes
  547. function getReflection( a, b ) {
  548. return a - ( b - a );
  549. }
  550. function parseFloats( string ) {
  551. var array = string.split( /[\s,]+|(?=\s?[+\-])/ );
  552. for ( var i = 0; i < array.length; i ++ ) {
  553. var number = array[ i ];
  554. // Handle values like 48.6037.7.8
  555. // TODO Find a regex for this
  556. if ( number.indexOf( '.' ) !== number.lastIndexOf( '.' ) ) {
  557. var split = number.split( '.' );
  558. for ( var s = 2; s < split.length; s ++ ) {
  559. array.splice( i + s - 1, 0, '0.' + split[ s ] );
  560. }
  561. }
  562. array[ i ] = parseFloat( number );
  563. }
  564. return array;
  565. }
  566. function getNodeTransform( node ) {
  567. if ( ! node.hasAttribute( 'transform' ) ) {
  568. return null;
  569. }
  570. var transform = parseTransformNode( node );
  571. if ( transform ) {
  572. if ( transformStack.length > 0 ) {
  573. transform.premultiply( transformStack[ transformStack.length - 1 ] );
  574. }
  575. currentTransform.copy( transform );
  576. transformStack.push( transform );
  577. }
  578. return transform;
  579. }
  580. function parseTransformNode( node ) {
  581. var transform = new THREE.Matrix3();
  582. var currentTransform = tempTransform0;
  583. var transformsTexts = node.getAttribute( 'transform' ).split( ' ' );
  584. for ( var tIndex = transformsTexts.length - 1; tIndex >= 0; tIndex-- ) {
  585. var transformText = transformsTexts[ tIndex ];
  586. var openParPos = transformText.indexOf( "(" );
  587. var closeParPos = transformText.indexOf( ")" );
  588. if ( openParPos > 0 && openParPos < closeParPos ) {
  589. var transformType = transformText.substr( 0, openParPos );
  590. var array = parseFloats( transformText.substr( openParPos + 1, closeParPos - openParPos - 1 ) );
  591. currentTransform.identity();
  592. switch ( transformType ) {
  593. case "translate":
  594. if ( array.length >= 1 ) {
  595. var tx = array[ 0 ];
  596. var ty = tx;
  597. if ( array.length >= 2 ) {
  598. ty = array[ 1 ];
  599. }
  600. currentTransform.translate( tx, ty );
  601. }
  602. break;
  603. case "rotate":
  604. if ( array.length >= 1 ) {
  605. var angle = 0;
  606. var cx = 0;
  607. var cy = 0;
  608. // Angle
  609. angle = - array[ 0 ] * Math.PI / 180;
  610. if ( array.length >= 3 ) {
  611. // Center x, y
  612. cx = array[ 1 ];
  613. cy = array[ 2 ];
  614. }
  615. // Rotate around center (cx, cy)
  616. tempTransform1.identity().translate( -cx, -cy );
  617. tempTransform2.identity().rotate( angle );
  618. tempTransform3.multiplyMatrices( tempTransform2, tempTransform1 );
  619. tempTransform1.identity().translate( cx, cy );
  620. currentTransform.multiplyMatrices( tempTransform1, tempTransform3 );
  621. }
  622. break;
  623. case "scale":
  624. if ( array.length >= 1 ) {
  625. var scaleX = array[ 0 ];
  626. var scaleY = scaleX;
  627. if ( array.length >= 2 ) {
  628. scaleY = array[ 1 ];
  629. }
  630. currentTransform.scale( scaleX, scaleY );
  631. }
  632. break;
  633. case "skewX":
  634. if ( array.length === 1 ) {
  635. currentTransform.set(
  636. 1, Math.tan( array[ 0 ] * Math.PI / 180 ), 0,
  637. 0, 1, 0,
  638. 0, 0, 1
  639. );
  640. }
  641. break;
  642. case "skewY":
  643. if ( array.length === 1 ) {
  644. currentTransform.set(
  645. 1, 0, 0,
  646. Math.tan( array[ 0 ] * Math.PI / 180 ), 1, 0,
  647. 0, 0, 1
  648. );
  649. }
  650. break;
  651. case "matrix":
  652. if ( array.length === 6 ) {
  653. currentTransform.set(
  654. array[ 0 ], array[ 2 ], array[ 4 ],
  655. array[ 1 ], array[ 3 ], array[ 5 ],
  656. 0, 0, 1
  657. );
  658. }
  659. break;
  660. }
  661. }
  662. transform.premultiply( currentTransform );
  663. }
  664. return transform;
  665. }
  666. function transformPath( path, m ) {
  667. function transfVec2( v2 ) {
  668. tempV3.set( v2.x, v2.y, 1 ).applyMatrix3( m );
  669. v2.set( tempV3.x, tempV3.y );
  670. }
  671. var isRotated = isTransformRotated( m );
  672. var tempV2 = new THREE.Vector2();
  673. var tempV3 = new THREE.Vector3();
  674. var subPaths = path.subPaths;
  675. for ( var i = 0, n = subPaths.length; i < n; i++ ) {
  676. var subPath = subPaths[ i ];
  677. var curves = subPath.curves;
  678. for ( var j = 0; j < curves.length; j++ ) {
  679. var curve = curves[ j ];
  680. if ( curve.isLineCurve ) {
  681. transfVec2( curve.v1 );
  682. transfVec2( curve.v2 );
  683. } else if ( curve.isCubicBezierCurve ) {
  684. transfVec2( curve.v0 );
  685. transfVec2( curve.v1 );
  686. transfVec2( curve.v2 );
  687. transfVec2( curve.v3 );
  688. } else if ( curve.isQuadraticBezierCurve ) {
  689. transfVec2( curve.v0 );
  690. transfVec2( curve.v1 );
  691. transfVec2( curve.v2 );
  692. } else if ( curve.isEllipseCurve ) {
  693. if ( isRotated ) {
  694. console.warn( "SVGLoader: Elliptic arc or ellipse rotation or skewing is not implemented." );
  695. }
  696. tempV2.set( curve.aX, curve.aY );
  697. transfVec2( tempV2 );
  698. curve.aX = tempV2.x;
  699. curve.aY = tempV2.y;
  700. curve.xRadius *= getTransformScaleX( m );
  701. curve.yRadius *= getTransformScaleY( m );
  702. }
  703. }
  704. }
  705. }
  706. function isTransformRotated( m ) {
  707. return m.elements[ 1 ] !== 0 || m.elements[ 3 ] !== 0;
  708. }
  709. function getTransformScaleX( m ) {
  710. var te = m.elements;
  711. return Math.sqrt( te[ 0 ] * te[ 0 ] + te[ 1 ] * te[ 1 ] )
  712. }
  713. function getTransformScaleY( m ) {
  714. var te = m.elements;
  715. return Math.sqrt( te[ 3 ] * te[ 3 ] + te[ 4 ] * te[ 4 ] )
  716. }
  717. //
  718. console.log( 'THREE.SVGLoader' );
  719. var paths = [];
  720. var transformStack = [];
  721. var tempTransform0 = new THREE.Matrix3();
  722. var tempTransform1 = new THREE.Matrix3();
  723. var tempTransform2 = new THREE.Matrix3();
  724. var tempTransform3 = new THREE.Matrix3();
  725. var currentTransform = new THREE.Matrix3();
  726. console.time( 'THREE.SVGLoader: DOMParser' );
  727. var xml = new DOMParser().parseFromString( text, 'image/svg+xml' ); // application/xml
  728. console.timeEnd( 'THREE.SVGLoader: DOMParser' );
  729. console.time( 'THREE.SVGLoader: Parse' );
  730. parseNode( xml.documentElement, { fill: '#000' } );
  731. this.rootNode = xml.documentElement;
  732. // console.log( paths );
  733. console.timeEnd( 'THREE.SVGLoader: Parse' );
  734. return paths;
  735. }
  736. };