SVGRenderer.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682
  1. import {
  2. Box2,
  3. Camera,
  4. Color,
  5. Matrix3,
  6. Matrix4,
  7. Object3D,
  8. SRGBColorSpace,
  9. Vector3
  10. } from 'three';
  11. import {
  12. Projector,
  13. RenderableFace,
  14. RenderableLine,
  15. RenderableSprite
  16. } from '../renderers/Projector.js';
  17. /**
  18. * Can be used to wrap SVG elements into a 3D object.
  19. *
  20. * @augments Object3D
  21. */
  22. class SVGObject extends Object3D {
  23. /**
  24. * Constructs a new SVG object.
  25. *
  26. * @param {SVGElement} node - The SVG element.
  27. */
  28. constructor( node ) {
  29. super();
  30. /**
  31. * This flag can be used for type testing.
  32. *
  33. * @type {boolean}
  34. * @readonly
  35. * @default true
  36. */
  37. this.isSVGObject = true;
  38. /**
  39. * This SVG element.
  40. *
  41. * @type {SVGElement}
  42. */
  43. this.node = node;
  44. }
  45. }
  46. /**
  47. * This renderer an be used to render geometric data using SVG. The produced vector
  48. * graphics are particular useful in the following use cases:
  49. *
  50. * - Animated logos or icons.
  51. * - Interactive 2D/3D diagrams or graphs.
  52. * - Interactive maps.
  53. * - Complex or animated user interfaces.
  54. *
  55. * `SVGRenderer` has various advantages. It produces crystal-clear and sharp output which
  56. * is independent of the actual viewport resolution.SVG elements can be styled via CSS.
  57. * And they have good accessibility since it's possible to add metadata like title or description
  58. * (useful for search engines or screen readers).
  59. *
  60. * There are, however, some important limitations:
  61. * - No advanced shading.
  62. * - No texture support.
  63. * - No shadow support.
  64. */
  65. class SVGRenderer {
  66. /**
  67. * Constructs a new SVG renderer.
  68. */
  69. constructor() {
  70. let _renderData, _elements, _lights,
  71. _svgWidth, _svgHeight, _svgWidthHalf, _svgHeightHalf,
  72. _v1, _v2, _v3,
  73. _svgNode,
  74. _pathCount = 0,
  75. _precision = null,
  76. _quality = 1,
  77. _currentPath, _currentStyle;
  78. const _this = this,
  79. _clipBox = new Box2(),
  80. _elemBox = new Box2(),
  81. _color = new Color(),
  82. _diffuseColor = new Color(),
  83. _ambientLight = new Color(),
  84. _directionalLights = new Color(),
  85. _pointLights = new Color(),
  86. _clearColor = new Color(),
  87. _vector3 = new Vector3(), // Needed for PointLight
  88. _centroid = new Vector3(),
  89. _normal = new Vector3(),
  90. _normalViewMatrix = new Matrix3(),
  91. _viewMatrix = new Matrix4(),
  92. _viewProjectionMatrix = new Matrix4(),
  93. _svgPathPool = [],
  94. _projector = new Projector(),
  95. _svg = document.createElementNS( 'http://www.w3.org/2000/svg', 'svg' );
  96. /**
  97. * The DOM where the renderer appends its child-elements.
  98. *
  99. * @type {DOMElement}
  100. */
  101. this.domElement = _svg;
  102. /**
  103. * Whether to automatically perform a clear before a render call or not.
  104. *
  105. * @type {boolean}
  106. * @default true
  107. */
  108. this.autoClear = true;
  109. /**
  110. * Whether to sort 3D objects or not.
  111. *
  112. * @type {boolean}
  113. * @default true
  114. */
  115. this.sortObjects = true;
  116. /**
  117. * Whether to sort elements or not.
  118. *
  119. * @type {boolean}
  120. * @default true
  121. */
  122. this.sortElements = true;
  123. /**
  124. * Number of fractional pixels to enlarge polygons in order to
  125. * prevent anti-aliasing gaps. Range is `[0,1]`.
  126. *
  127. * @type {number}
  128. * @default 0.5
  129. */
  130. this.overdraw = 0.5;
  131. /**
  132. * The output color space.
  133. *
  134. * @type {(SRGBColorSpace|LinearSRGBColorSpace)}
  135. * @default SRGBColorSpace
  136. */
  137. this.outputColorSpace = SRGBColorSpace;
  138. /**
  139. * Provides information about the number of
  140. * rendered vertices and faces.
  141. *
  142. * @type {Object}
  143. */
  144. this.info = {
  145. render: {
  146. vertices: 0,
  147. faces: 0
  148. }
  149. };
  150. /**
  151. * Sets the render quality. Setting to `high` means This value indicates that the browser
  152. * tries to improve the SVG quality over rendering speed and geometric precision.
  153. *
  154. * @param {('low'|'high')} quality - The quality.
  155. */
  156. this.setQuality = function ( quality ) {
  157. switch ( quality ) {
  158. case 'high': _quality = 1; break;
  159. case 'low': _quality = 0; break;
  160. }
  161. };
  162. /**
  163. * Sets the clear color.
  164. *
  165. * @param {(number|Color|string)} color - The clear color to set.
  166. */
  167. this.setClearColor = function ( color ) {
  168. _clearColor.set( color );
  169. };
  170. this.setPixelRatio = function () {};
  171. /**
  172. * Resizes the renderer to the given width and height.
  173. *
  174. * @param {number} width - The width of the renderer.
  175. * @param {number} height - The height of the renderer.
  176. */
  177. this.setSize = function ( width, height ) {
  178. _svgWidth = width; _svgHeight = height;
  179. _svgWidthHalf = _svgWidth / 2; _svgHeightHalf = _svgHeight / 2;
  180. _svg.setAttribute( 'viewBox', ( - _svgWidthHalf ) + ' ' + ( - _svgHeightHalf ) + ' ' + _svgWidth + ' ' + _svgHeight );
  181. _svg.setAttribute( 'width', _svgWidth );
  182. _svg.setAttribute( 'height', _svgHeight );
  183. _clipBox.min.set( - _svgWidthHalf, - _svgHeightHalf );
  184. _clipBox.max.set( _svgWidthHalf, _svgHeightHalf );
  185. };
  186. /**
  187. * Returns an object containing the width and height of the renderer.
  188. *
  189. * @return {{width:number,height:number}} The size of the renderer.
  190. */
  191. this.getSize = function () {
  192. return {
  193. width: _svgWidth,
  194. height: _svgHeight
  195. };
  196. };
  197. /**
  198. * Sets the precision of the data used to create a paths.
  199. *
  200. * @param {number} precision - The precision to set.
  201. */
  202. this.setPrecision = function ( precision ) {
  203. _precision = precision;
  204. };
  205. function removeChildNodes() {
  206. _pathCount = 0;
  207. while ( _svg.childNodes.length > 0 ) {
  208. _svg.removeChild( _svg.childNodes[ 0 ] );
  209. }
  210. }
  211. function convert( c ) {
  212. return _precision !== null ? c.toFixed( _precision ) : c;
  213. }
  214. /**
  215. * Performs a manual clear with the defined clear color.
  216. */
  217. this.clear = function () {
  218. removeChildNodes();
  219. _svg.style.backgroundColor = _clearColor.getStyle( _this.outputColorSpace );
  220. };
  221. /**
  222. * Renders the given scene using the given camera.
  223. *
  224. * @param {Object3D} scene - A scene or any other type of 3D object.
  225. * @param {Camera} camera - The camera.
  226. */
  227. this.render = function ( scene, camera ) {
  228. if ( camera instanceof Camera === false ) {
  229. console.error( 'THREE.SVGRenderer.render: camera is not an instance of Camera.' );
  230. return;
  231. }
  232. const background = scene.background;
  233. if ( background && background.isColor ) {
  234. removeChildNodes();
  235. _svg.style.backgroundColor = background.getStyle( _this.outputColorSpace );
  236. } else if ( this.autoClear === true ) {
  237. this.clear();
  238. }
  239. _this.info.render.vertices = 0;
  240. _this.info.render.faces = 0;
  241. _viewMatrix.copy( camera.matrixWorldInverse );
  242. _viewProjectionMatrix.multiplyMatrices( camera.projectionMatrix, _viewMatrix );
  243. _renderData = _projector.projectScene( scene, camera, this.sortObjects, this.sortElements );
  244. _elements = _renderData.elements;
  245. _lights = _renderData.lights;
  246. _normalViewMatrix.getNormalMatrix( camera.matrixWorldInverse );
  247. calculateLights( _lights );
  248. // reset accumulated path
  249. _currentPath = '';
  250. _currentStyle = '';
  251. for ( let e = 0, el = _elements.length; e < el; e ++ ) {
  252. const element = _elements[ e ];
  253. const material = element.material;
  254. if ( material === undefined || material.opacity === 0 ) continue;
  255. _elemBox.makeEmpty();
  256. if ( element instanceof RenderableSprite ) {
  257. _v1 = element;
  258. _v1.x *= _svgWidthHalf; _v1.y *= - _svgHeightHalf;
  259. renderSprite( _v1, element, material );
  260. } else if ( element instanceof RenderableLine ) {
  261. _v1 = element.v1; _v2 = element.v2;
  262. _v1.positionScreen.x *= _svgWidthHalf; _v1.positionScreen.y *= - _svgHeightHalf;
  263. _v2.positionScreen.x *= _svgWidthHalf; _v2.positionScreen.y *= - _svgHeightHalf;
  264. _elemBox.setFromPoints( [ _v1.positionScreen, _v2.positionScreen ] );
  265. if ( _clipBox.intersectsBox( _elemBox ) === true ) {
  266. renderLine( _v1, _v2, material );
  267. }
  268. } else if ( element instanceof RenderableFace ) {
  269. _v1 = element.v1; _v2 = element.v2; _v3 = element.v3;
  270. if ( _v1.positionScreen.z < - 1 || _v1.positionScreen.z > 1 ) continue;
  271. if ( _v2.positionScreen.z < - 1 || _v2.positionScreen.z > 1 ) continue;
  272. if ( _v3.positionScreen.z < - 1 || _v3.positionScreen.z > 1 ) continue;
  273. _v1.positionScreen.x *= _svgWidthHalf; _v1.positionScreen.y *= - _svgHeightHalf;
  274. _v2.positionScreen.x *= _svgWidthHalf; _v2.positionScreen.y *= - _svgHeightHalf;
  275. _v3.positionScreen.x *= _svgWidthHalf; _v3.positionScreen.y *= - _svgHeightHalf;
  276. if ( this.overdraw > 0 ) {
  277. expand( _v1.positionScreen, _v2.positionScreen, this.overdraw );
  278. expand( _v2.positionScreen, _v3.positionScreen, this.overdraw );
  279. expand( _v3.positionScreen, _v1.positionScreen, this.overdraw );
  280. }
  281. _elemBox.setFromPoints( [
  282. _v1.positionScreen,
  283. _v2.positionScreen,
  284. _v3.positionScreen
  285. ] );
  286. if ( _clipBox.intersectsBox( _elemBox ) === true ) {
  287. renderFace3( _v1, _v2, _v3, element, material );
  288. }
  289. }
  290. }
  291. flushPath(); // just to flush last svg:path
  292. scene.traverseVisible( function ( object ) {
  293. if ( object.isSVGObject ) {
  294. _vector3.setFromMatrixPosition( object.matrixWorld );
  295. _vector3.applyMatrix4( _viewProjectionMatrix );
  296. if ( _vector3.z < - 1 || _vector3.z > 1 ) return;
  297. const x = _vector3.x * _svgWidthHalf;
  298. const y = - _vector3.y * _svgHeightHalf;
  299. const node = object.node;
  300. node.setAttribute( 'transform', 'translate(' + x + ',' + y + ')' );
  301. _svg.appendChild( node );
  302. }
  303. } );
  304. };
  305. function calculateLights( lights ) {
  306. _ambientLight.setRGB( 0, 0, 0 );
  307. _directionalLights.setRGB( 0, 0, 0 );
  308. _pointLights.setRGB( 0, 0, 0 );
  309. for ( let l = 0, ll = lights.length; l < ll; l ++ ) {
  310. const light = lights[ l ];
  311. const lightColor = light.color;
  312. if ( light.isAmbientLight ) {
  313. _ambientLight.r += lightColor.r;
  314. _ambientLight.g += lightColor.g;
  315. _ambientLight.b += lightColor.b;
  316. } else if ( light.isDirectionalLight ) {
  317. _directionalLights.r += lightColor.r;
  318. _directionalLights.g += lightColor.g;
  319. _directionalLights.b += lightColor.b;
  320. } else if ( light.isPointLight ) {
  321. _pointLights.r += lightColor.r;
  322. _pointLights.g += lightColor.g;
  323. _pointLights.b += lightColor.b;
  324. }
  325. }
  326. }
  327. function calculateLight( lights, position, normal, color ) {
  328. for ( let l = 0, ll = lights.length; l < ll; l ++ ) {
  329. const light = lights[ l ];
  330. const lightColor = light.color;
  331. if ( light.isDirectionalLight ) {
  332. const lightPosition = _vector3.setFromMatrixPosition( light.matrixWorld ).normalize();
  333. let amount = normal.dot( lightPosition );
  334. if ( amount <= 0 ) continue;
  335. amount *= light.intensity;
  336. color.r += lightColor.r * amount;
  337. color.g += lightColor.g * amount;
  338. color.b += lightColor.b * amount;
  339. } else if ( light.isPointLight ) {
  340. const lightPosition = _vector3.setFromMatrixPosition( light.matrixWorld );
  341. let amount = normal.dot( _vector3.subVectors( lightPosition, position ).normalize() );
  342. if ( amount <= 0 ) continue;
  343. amount *= light.distance == 0 ? 1 : 1 - Math.min( position.distanceTo( lightPosition ) / light.distance, 1 );
  344. if ( amount == 0 ) continue;
  345. amount *= light.intensity;
  346. color.r += lightColor.r * amount;
  347. color.g += lightColor.g * amount;
  348. color.b += lightColor.b * amount;
  349. }
  350. }
  351. }
  352. function renderSprite( v1, element, material ) {
  353. let scaleX = element.scale.x * _svgWidthHalf;
  354. let scaleY = element.scale.y * _svgHeightHalf;
  355. if ( material.isPointsMaterial ) {
  356. scaleX *= material.size;
  357. scaleY *= material.size;
  358. }
  359. const path = 'M' + convert( v1.x - scaleX * 0.5 ) + ',' + convert( v1.y - scaleY * 0.5 ) + 'h' + convert( scaleX ) + 'v' + convert( scaleY ) + 'h' + convert( - scaleX ) + 'z';
  360. let style = '';
  361. if ( material.isSpriteMaterial || material.isPointsMaterial ) {
  362. style = 'fill:' + material.color.getStyle( _this.outputColorSpace ) + ';fill-opacity:' + material.opacity;
  363. }
  364. addPath( style, path );
  365. }
  366. function renderLine( v1, v2, material ) {
  367. const path = 'M' + convert( v1.positionScreen.x ) + ',' + convert( v1.positionScreen.y ) + 'L' + convert( v2.positionScreen.x ) + ',' + convert( v2.positionScreen.y );
  368. if ( material.isLineBasicMaterial ) {
  369. let style = 'fill:none;stroke:' + material.color.getStyle( _this.outputColorSpace ) + ';stroke-opacity:' + material.opacity + ';stroke-width:' + material.linewidth + ';stroke-linecap:' + material.linecap;
  370. if ( material.isLineDashedMaterial ) {
  371. style = style + ';stroke-dasharray:' + material.dashSize + ',' + material.gapSize;
  372. }
  373. addPath( style, path );
  374. }
  375. }
  376. function renderFace3( v1, v2, v3, element, material ) {
  377. _this.info.render.vertices += 3;
  378. _this.info.render.faces ++;
  379. const path = 'M' + convert( v1.positionScreen.x ) + ',' + convert( v1.positionScreen.y ) + 'L' + convert( v2.positionScreen.x ) + ',' + convert( v2.positionScreen.y ) + 'L' + convert( v3.positionScreen.x ) + ',' + convert( v3.positionScreen.y ) + 'z';
  380. let style = '';
  381. if ( material.isMeshBasicMaterial ) {
  382. _color.copy( material.color );
  383. if ( material.vertexColors ) {
  384. _color.multiply( element.color );
  385. }
  386. } else if ( material.isMeshLambertMaterial || material.isMeshPhongMaterial || material.isMeshStandardMaterial ) {
  387. _diffuseColor.copy( material.color );
  388. if ( material.vertexColors ) {
  389. _diffuseColor.multiply( element.color );
  390. }
  391. _color.copy( _ambientLight );
  392. _centroid.copy( v1.positionWorld ).add( v2.positionWorld ).add( v3.positionWorld ).divideScalar( 3 );
  393. calculateLight( _lights, _centroid, element.normalModel, _color );
  394. _color.multiply( _diffuseColor ).add( material.emissive );
  395. } else if ( material.isMeshNormalMaterial ) {
  396. _normal.copy( element.normalModel ).applyMatrix3( _normalViewMatrix ).normalize();
  397. _color.setRGB( _normal.x, _normal.y, _normal.z ).multiplyScalar( 0.5 ).addScalar( 0.5 );
  398. }
  399. if ( material.wireframe ) {
  400. style = 'fill:none;stroke:' + _color.getStyle( _this.outputColorSpace ) + ';stroke-opacity:' + material.opacity + ';stroke-width:' + material.wireframeLinewidth + ';stroke-linecap:' + material.wireframeLinecap + ';stroke-linejoin:' + material.wireframeLinejoin;
  401. } else {
  402. style = 'fill:' + _color.getStyle( _this.outputColorSpace ) + ';fill-opacity:' + material.opacity;
  403. }
  404. addPath( style, path );
  405. }
  406. // Hide anti-alias gaps
  407. function expand( v1, v2, pixels ) {
  408. let x = v2.x - v1.x, y = v2.y - v1.y;
  409. const det = x * x + y * y;
  410. if ( det === 0 ) return;
  411. const idet = pixels / Math.sqrt( det );
  412. x *= idet; y *= idet;
  413. v2.x += x; v2.y += y;
  414. v1.x -= x; v1.y -= y;
  415. }
  416. function addPath( style, path ) {
  417. if ( _currentStyle === style ) {
  418. _currentPath += path;
  419. } else {
  420. flushPath();
  421. _currentStyle = style;
  422. _currentPath = path;
  423. }
  424. }
  425. function flushPath() {
  426. if ( _currentPath ) {
  427. _svgNode = getPathNode( _pathCount ++ );
  428. _svgNode.setAttribute( 'd', _currentPath );
  429. _svgNode.setAttribute( 'style', _currentStyle );
  430. _svg.appendChild( _svgNode );
  431. }
  432. _currentPath = '';
  433. _currentStyle = '';
  434. }
  435. function getPathNode( id ) {
  436. if ( _svgPathPool[ id ] == null ) {
  437. _svgPathPool[ id ] = document.createElementNS( 'http://www.w3.org/2000/svg', 'path' );
  438. if ( _quality == 0 ) {
  439. _svgPathPool[ id ].setAttribute( 'shape-rendering', 'crispEdges' ); //optimizeSpeed
  440. }
  441. return _svgPathPool[ id ];
  442. }
  443. return _svgPathPool[ id ];
  444. }
  445. }
  446. }
  447. export { SVGObject, SVGRenderer };
粤ICP备19079148号