SVGRenderer.js 17 KB

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