SVGRenderer.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818
  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` means This value indicates that the browser
  159. * tries to improve the SVG quality 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. function arraySortStable( array, start, length ) {
  233. // A stable insertion sort for sorting the render list
  234. // This avoids the GC overhead of Array.prototype.sort()
  235. for ( let i = start + 1; i < start + length; i ++ ) {
  236. const item = array[ i ];
  237. let j = i - 1;
  238. while ( j >= start && renderSort( array[ j ], item ) > 0 ) {
  239. array[ j + 1 ] = array[ j ];
  240. j --;
  241. }
  242. array[ j + 1 ] = item;
  243. }
  244. }
  245. /**
  246. * Performs a manual clear with the defined clear color.
  247. */
  248. this.clear = function () {
  249. removeChildNodes();
  250. _svg.style.backgroundColor = _clearColor.getStyle( _this.outputColorSpace );
  251. };
  252. /**
  253. * Renders the given scene using the given camera.
  254. *
  255. * @param {Object3D} scene - A scene or any other type of 3D object.
  256. * @param {Camera} camera - The camera.
  257. */
  258. this.render = function ( scene, camera ) {
  259. if ( camera instanceof Camera === false ) {
  260. console.error( 'THREE.SVGRenderer.render: camera is not an instance of Camera.' );
  261. return;
  262. }
  263. const background = scene.background;
  264. if ( background && background.isColor ) {
  265. removeChildNodes();
  266. _svg.style.backgroundColor = background.getStyle( _this.outputColorSpace );
  267. } else if ( this.autoClear === true ) {
  268. this.clear();
  269. }
  270. _this.info.render.vertices = 0;
  271. _this.info.render.faces = 0;
  272. _viewMatrix.copy( camera.matrixWorldInverse );
  273. _viewProjectionMatrix.multiplyMatrices( camera.projectionMatrix, _viewMatrix );
  274. _renderData = _projector.projectScene( scene, camera, this.sortObjects, this.sortElements );
  275. _elements = _renderData.elements;
  276. _lights = _renderData.lights;
  277. _normalViewMatrix.getNormalMatrix( camera.matrixWorldInverse );
  278. calculateLights( _lights );
  279. _renderListCount = 0;
  280. for ( let e = 0, el = _elements.length; e < el; e ++ ) {
  281. const element = _elements[ e ];
  282. const material = element.material;
  283. if ( material === undefined || material.opacity === 0 ) continue;
  284. getRenderItem( _renderListCount ++, 'element', element, material );
  285. }
  286. _svgObjectCount = 0;
  287. scene.traverseVisible( function ( object ) {
  288. if ( object.isSVGObject ) {
  289. _vector3.setFromMatrixPosition( object.matrixWorld );
  290. _vector3.applyMatrix4( _viewProjectionMatrix );
  291. if ( _vector3.z < - 1 || _vector3.z > 1 ) return;
  292. const x = _vector3.x * _svgWidthHalf;
  293. const y = - _vector3.y * _svgHeightHalf;
  294. const svgObject = getSVGObjectData( _svgObjectCount ++ );
  295. svgObject.node = object.node;
  296. svgObject.x = x;
  297. svgObject.y = y;
  298. svgObject.z = _vector3.z;
  299. svgObject.renderOrder = object.renderOrder;
  300. getRenderItem( _renderListCount ++, 'svgObject', svgObject, null );
  301. }
  302. } );
  303. if ( this.sortElements ) {
  304. arraySortStable( _renderListPool, 0, _renderListCount );
  305. }
  306. // Reset accumulated path
  307. _currentPath = '';
  308. _currentStyle = '';
  309. // Render in sorted order
  310. for ( let i = 0; i < _renderListCount; i ++ ) {
  311. const item = _renderListPool[ i ];
  312. if ( item.type === 'svgObject' ) {
  313. flushPath(); // Flush any accumulated paths before inserting SVG node
  314. const svgObject = item.data;
  315. const node = svgObject.node;
  316. node.setAttribute( 'transform', 'translate(' + svgObject.x + ',' + svgObject.y + ')' );
  317. _svg.appendChild( node );
  318. } else {
  319. const element = item.data;
  320. const material = item.material;
  321. _elemBox.makeEmpty();
  322. if ( element instanceof RenderableSprite ) {
  323. _v1 = element;
  324. _v1.x *= _svgWidthHalf; _v1.y *= - _svgHeightHalf;
  325. renderSprite( _v1, element, material );
  326. } else if ( element instanceof RenderableLine ) {
  327. _v1 = element.v1; _v2 = element.v2;
  328. _v1.positionScreen.x *= _svgWidthHalf; _v1.positionScreen.y *= - _svgHeightHalf;
  329. _v2.positionScreen.x *= _svgWidthHalf; _v2.positionScreen.y *= - _svgHeightHalf;
  330. _elemBox.setFromPoints( [ _v1.positionScreen, _v2.positionScreen ] );
  331. if ( _clipBox.intersectsBox( _elemBox ) === true ) {
  332. renderLine( _v1, _v2, material );
  333. }
  334. } else if ( element instanceof RenderableFace ) {
  335. _v1 = element.v1; _v2 = element.v2; _v3 = element.v3;
  336. _v1.positionScreen.x *= _svgWidthHalf; _v1.positionScreen.y *= - _svgHeightHalf;
  337. _v2.positionScreen.x *= _svgWidthHalf; _v2.positionScreen.y *= - _svgHeightHalf;
  338. _v3.positionScreen.x *= _svgWidthHalf; _v3.positionScreen.y *= - _svgHeightHalf;
  339. if ( this.overdraw > 0 ) {
  340. expand( _v1.positionScreen, _v2.positionScreen, this.overdraw );
  341. expand( _v2.positionScreen, _v3.positionScreen, this.overdraw );
  342. expand( _v3.positionScreen, _v1.positionScreen, this.overdraw );
  343. }
  344. _elemBox.setFromPoints( [
  345. _v1.positionScreen,
  346. _v2.positionScreen,
  347. _v3.positionScreen
  348. ] );
  349. if ( _clipBox.intersectsBox( _elemBox ) === true ) {
  350. renderFace3( _v1, _v2, _v3, element, material );
  351. }
  352. }
  353. }
  354. }
  355. flushPath(); // Flush any remaining paths
  356. };
  357. function calculateLights( lights ) {
  358. _ambientLight.setRGB( 0, 0, 0 );
  359. _directionalLights.setRGB( 0, 0, 0 );
  360. _pointLights.setRGB( 0, 0, 0 );
  361. for ( let l = 0, ll = lights.length; l < ll; l ++ ) {
  362. const light = lights[ l ];
  363. const lightColor = light.color;
  364. if ( light.isAmbientLight ) {
  365. _ambientLight.r += lightColor.r;
  366. _ambientLight.g += lightColor.g;
  367. _ambientLight.b += lightColor.b;
  368. } else if ( light.isDirectionalLight ) {
  369. _directionalLights.r += lightColor.r;
  370. _directionalLights.g += lightColor.g;
  371. _directionalLights.b += lightColor.b;
  372. } else if ( light.isPointLight ) {
  373. _pointLights.r += lightColor.r;
  374. _pointLights.g += lightColor.g;
  375. _pointLights.b += lightColor.b;
  376. }
  377. }
  378. }
  379. function calculateLight( lights, position, normal, color ) {
  380. for ( let l = 0, ll = lights.length; l < ll; l ++ ) {
  381. const light = lights[ l ];
  382. const lightColor = light.color;
  383. if ( light.isDirectionalLight ) {
  384. const lightPosition = _vector3.setFromMatrixPosition( light.matrixWorld ).normalize();
  385. let amount = normal.dot( lightPosition );
  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. } else if ( light.isPointLight ) {
  392. const lightPosition = _vector3.setFromMatrixPosition( light.matrixWorld );
  393. let amount = normal.dot( _vector3.subVectors( lightPosition, position ).normalize() );
  394. if ( amount <= 0 ) continue;
  395. amount *= light.distance == 0 ? 1 : 1 - Math.min( position.distanceTo( lightPosition ) / light.distance, 1 );
  396. if ( amount == 0 ) continue;
  397. amount *= light.intensity;
  398. color.r += lightColor.r * amount;
  399. color.g += lightColor.g * amount;
  400. color.b += lightColor.b * amount;
  401. }
  402. }
  403. }
  404. function renderSprite( v1, element, material ) {
  405. let scaleX = element.scale.x * _svgWidthHalf;
  406. let scaleY = element.scale.y * _svgHeightHalf;
  407. if ( material.isPointsMaterial ) {
  408. scaleX *= material.size;
  409. scaleY *= material.size;
  410. }
  411. 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';
  412. let style = '';
  413. if ( material.isSpriteMaterial || material.isPointsMaterial ) {
  414. style = 'fill:' + material.color.getStyle( _this.outputColorSpace ) + ';fill-opacity:' + material.opacity;
  415. }
  416. addPath( style, path );
  417. }
  418. function renderLine( v1, v2, material ) {
  419. const path = 'M' + convert( v1.positionScreen.x ) + ',' + convert( v1.positionScreen.y ) + 'L' + convert( v2.positionScreen.x ) + ',' + convert( v2.positionScreen.y );
  420. if ( material.isLineBasicMaterial ) {
  421. let style = 'fill:none;stroke:' + material.color.getStyle( _this.outputColorSpace ) + ';stroke-opacity:' + material.opacity + ';stroke-width:' + material.linewidth + ';stroke-linecap:' + material.linecap;
  422. if ( material.isLineDashedMaterial ) {
  423. style = style + ';stroke-dasharray:' + material.dashSize + ',' + material.gapSize;
  424. }
  425. addPath( style, path );
  426. }
  427. }
  428. function renderFace3( v1, v2, v3, element, material ) {
  429. _this.info.render.vertices += 3;
  430. _this.info.render.faces ++;
  431. 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';
  432. let style = '';
  433. if ( material.isMeshBasicMaterial ) {
  434. _color.copy( material.color );
  435. if ( material.vertexColors ) {
  436. _color.multiply( element.color );
  437. }
  438. } else if ( material.isMeshLambertMaterial || material.isMeshPhongMaterial || material.isMeshStandardMaterial ) {
  439. _diffuseColor.copy( material.color );
  440. if ( material.vertexColors ) {
  441. _diffuseColor.multiply( element.color );
  442. }
  443. _color.copy( _ambientLight );
  444. _centroid.copy( v1.positionWorld ).add( v2.positionWorld ).add( v3.positionWorld ).divideScalar( 3 );
  445. calculateLight( _lights, _centroid, element.normalModel, _color );
  446. _color.multiply( _diffuseColor ).add( material.emissive );
  447. } else if ( material.isMeshNormalMaterial ) {
  448. _normal.copy( element.normalModel ).applyMatrix3( _normalViewMatrix ).normalize();
  449. _color.setRGB( _normal.x, _normal.y, _normal.z ).multiplyScalar( 0.5 ).addScalar( 0.5 );
  450. }
  451. if ( material.wireframe ) {
  452. style = 'fill:none;stroke:' + _color.getStyle( _this.outputColorSpace ) + ';stroke-opacity:' + material.opacity + ';stroke-width:' + material.wireframeLinewidth + ';stroke-linecap:' + material.wireframeLinecap + ';stroke-linejoin:' + material.wireframeLinejoin;
  453. } else {
  454. style = 'fill:' + _color.getStyle( _this.outputColorSpace ) + ';fill-opacity:' + material.opacity;
  455. }
  456. addPath( style, path );
  457. }
  458. // Hide anti-alias gaps
  459. function expand( v1, v2, pixels ) {
  460. let x = v2.x - v1.x, y = v2.y - v1.y;
  461. const det = x * x + y * y;
  462. if ( det === 0 ) return;
  463. const idet = pixels / Math.sqrt( det );
  464. x *= idet; y *= idet;
  465. v2.x += x; v2.y += y;
  466. v1.x -= x; v1.y -= y;
  467. }
  468. function addPath( style, path ) {
  469. if ( _currentStyle === style ) {
  470. _currentPath += path;
  471. } else {
  472. flushPath();
  473. _currentStyle = style;
  474. _currentPath = path;
  475. }
  476. }
  477. function flushPath() {
  478. if ( _currentPath ) {
  479. _svgNode = getPathNode( _pathCount ++ );
  480. _svgNode.setAttribute( 'd', _currentPath );
  481. _svgNode.setAttribute( 'style', _currentStyle );
  482. _svg.appendChild( _svgNode );
  483. }
  484. _currentPath = '';
  485. _currentStyle = '';
  486. }
  487. function getPathNode( id ) {
  488. let path = _svgPathPool[ id ];
  489. if ( path === undefined ) {
  490. path = document.createElementNS( 'http://www.w3.org/2000/svg', 'path' );
  491. if ( _quality == 0 ) {
  492. path.setAttribute( 'shape-rendering', 'crispEdges' ); //optimizeSpeed
  493. }
  494. _svgPathPool[ id ] = path;
  495. }
  496. return path;
  497. }
  498. function getSVGObjectData( id ) {
  499. let svgObject = _svgObjectsPool[ id ];
  500. if ( svgObject === undefined ) {
  501. svgObject = {
  502. node: null,
  503. x: 0,
  504. y: 0,
  505. z: 0,
  506. renderOrder: 0
  507. };
  508. _svgObjectsPool[ id ] = svgObject;
  509. }
  510. return svgObject;
  511. }
  512. function getRenderItem( id, type, data, material ) {
  513. let item = _renderListPool[ id ];
  514. if ( item === undefined ) {
  515. item = {
  516. type: type,
  517. data: data,
  518. material: material
  519. };
  520. _renderListPool[ id ] = item;
  521. return item;
  522. }
  523. item.type = type;
  524. item.data = data;
  525. item.material = material;
  526. return item;
  527. }
  528. }
  529. }
  530. export { SVGObject, SVGRenderer };
粤ICP备19079148号