@@ -88,6 +88,8 @@ class SVGRenderer {
8888
8989 _svgNode ,
9090 _pathCount = 0 ,
91+ _svgObjectCount = 0 ,
92+ _renderListCount = 0 ,
9193
9294 _precision = null ,
9395 _quality = 1 ,
@@ -114,6 +116,8 @@ class SVGRenderer {
114116 _viewProjectionMatrix = new Matrix4 ( ) ,
115117
116118 _svgPathPool = [ ] ,
119+ _svgObjectsPool = [ ] ,
120+ _renderListPool = [ ] ,
117121
118122 _projector = new Projector ( ) ,
119123 _svg = document . createElementNS ( 'http://www.w3.org/2000/svg' , 'svg' ) ;
@@ -276,6 +280,49 @@ class SVGRenderer {
276280
277281 }
278282
283+ function renderSort ( a , b ) {
284+
285+ const aOrder = a . data . renderOrder !== undefined ? a . data . renderOrder : 0 ;
286+ const bOrder = b . data . renderOrder !== undefined ? b . data . renderOrder : 0 ;
287+
288+ if ( aOrder !== bOrder ) {
289+
290+ return aOrder - bOrder ;
291+
292+ } else {
293+
294+ const aZ = a . data . z !== undefined ? a . data . z : 0 ;
295+ const bZ = b . data . z !== undefined ? b . data . z : 0 ;
296+
297+ return bZ - aZ ; // Painter's algorithm: far to near
298+
299+ }
300+
301+ }
302+
303+ function arraySortStable ( array , start , length ) {
304+
305+ // A stable insertion sort for sorting the render list
306+ // This avoids the GC overhead of Array.prototype.sort()
307+
308+ for ( let i = start + 1 ; i < start + length ; i ++ ) {
309+
310+ const item = array [ i ] ;
311+ let j = i - 1 ;
312+
313+ while ( j >= start && renderSort ( array [ j ] , item ) > 0 ) {
314+
315+ array [ j + 1 ] = array [ j ] ;
316+ j -- ;
317+
318+ }
319+
320+ array [ j + 1 ] = item ;
321+
322+ }
323+
324+ }
325+
279326 /**
280327 * Performs a manual clear with the defined clear color.
281328 */
@@ -328,10 +375,7 @@ class SVGRenderer {
328375
329376 calculateLights ( _lights ) ;
330377
331- // reset accumulated path
332-
333- _currentPath = '' ;
334- _currentStyle = '' ;
378+ _renderListCount = 0 ;
335379
336380 for ( let e = 0 , el = _elements . length ; e < el ; e ++ ) {
337381
@@ -340,84 +384,126 @@ class SVGRenderer {
340384
341385 if ( material === undefined || material . opacity === 0 ) continue ;
342386
343- _elemBox . makeEmpty ( ) ;
387+ getRenderItem ( _renderListCount ++ , 'element' , element , material ) ;
344388
345- if ( element instanceof RenderableSprite ) {
389+ }
346390
347- _v1 = element ;
348- _v1 . x *= _svgWidthHalf ; _v1 . y *= - _svgHeightHalf ;
391+ _svgObjectCount = 0 ;
349392
350- renderSprite ( _v1 , element , material ) ;
393+ scene . traverseVisible ( function ( object ) {
351394
352- } else if ( element instanceof RenderableLine ) {
395+ if ( object . isSVGObject ) {
353396
354- _v1 = element . v1 ; _v2 = element . v2 ;
397+ _vector3 . setFromMatrixPosition ( object . matrixWorld ) ;
398+ _vector3 . applyMatrix4 ( _viewProjectionMatrix ) ;
355399
356- _v1 . positionScreen . x *= _svgWidthHalf ; _v1 . positionScreen . y *= - _svgHeightHalf ;
357- _v2 . positionScreen . x *= _svgWidthHalf ; _v2 . positionScreen . y *= - _svgHeightHalf ;
400+ if ( _vector3 . z < - 1 || _vector3 . z > 1 ) return ;
358401
359- _elemBox . setFromPoints ( [ _v1 . positionScreen , _v2 . positionScreen ] ) ;
402+ const x = _vector3 . x * _svgWidthHalf ;
403+ const y = - _vector3 . y * _svgHeightHalf ;
360404
361- if ( _clipBox . intersectsBox ( _elemBox ) === true ) {
405+ const svgObject = getSVGObjectData ( _svgObjectCount ++ ) ;
362406
363- renderLine ( _v1 , _v2 , material ) ;
407+ svgObject . node = object . node ;
408+ svgObject . x = x ;
409+ svgObject . y = y ;
410+ svgObject . z = _vector3 . z ;
411+ svgObject . renderOrder = object . renderOrder ;
364412
365- }
413+ getRenderItem ( _renderListCount ++ , 'svgObject' , svgObject , null ) ;
366414
367- } else if ( element instanceof RenderableFace ) {
415+ }
368416
369- _v1 = element . v1 ; _v2 = element . v2 ; _v3 = element . v3 ;
417+ } ) ;
370418
371- _v1 . positionScreen . x *= _svgWidthHalf ; _v1 . positionScreen . y *= - _svgHeightHalf ;
372- _v2 . positionScreen . x *= _svgWidthHalf ; _v2 . positionScreen . y *= - _svgHeightHalf ;
373- _v3 . positionScreen . x *= _svgWidthHalf ; _v3 . positionScreen . y *= - _svgHeightHalf ;
419+ if ( this . sortElements ) {
374420
375- if ( this . overdraw > 0 ) {
421+ arraySortStable ( _renderListPool , 0 , _renderListCount ) ;
376422
377- expand ( _v1 . positionScreen , _v2 . positionScreen , this . overdraw ) ;
378- expand ( _v2 . positionScreen , _v3 . positionScreen , this . overdraw ) ;
379- expand ( _v3 . positionScreen , _v1 . positionScreen , this . overdraw ) ;
423+ }
380424
381- }
425+ // Reset accumulated path
426+ _currentPath = '' ;
427+ _currentStyle = '' ;
382428
383- _elemBox . setFromPoints ( [
384- _v1 . positionScreen ,
385- _v2 . positionScreen ,
386- _v3 . positionScreen
387- ] ) ;
429+ // Render in sorted order
430+ for ( let i = 0 ; i < _renderListCount ; i ++ ) {
388431
389- if ( _clipBox . intersectsBox ( _elemBox ) === true ) {
432+ const item = _renderListPool [ i ] ;
390433
391- renderFace3 ( _v1 , _v2 , _v3 , element , material ) ;
434+ if ( item . type === 'svgObject' ) {
392435
393- }
436+ flushPath ( ) ; // Flush any accumulated paths before inserting SVG node
394437
395- }
438+ const svgObject = item . data ;
439+ const node = svgObject . node ;
440+ node . setAttribute ( 'transform' , 'translate(' + svgObject . x + ',' + svgObject . y + ')' ) ;
441+ _svg . appendChild ( node ) ;
396442
397- }
443+ } else {
398444
399- flushPath ( ) ; // just to flush last svg:path
445+ const element = item . data ;
446+ const material = item . material ;
400447
401- scene . traverseVisible ( function ( object ) {
448+ _elemBox . makeEmpty ( ) ;
402449
403- if ( object . isSVGObject ) {
450+ if ( element instanceof RenderableSprite ) {
404451
405- _vector3 . setFromMatrixPosition ( object . matrixWorld ) ;
406- _vector3 . applyMatrix4 ( _viewProjectionMatrix ) ;
452+ _v1 = element ;
453+ _v1 . x *= _svgWidthHalf ; _v1 . y *= - _svgHeightHalf ;
407454
408- if ( _vector3 . z < - 1 || _vector3 . z > 1 ) return ;
455+ renderSprite ( _v1 , element , material ) ;
409456
410- const x = _vector3 . x * _svgWidthHalf ;
411- const y = - _vector3 . y * _svgHeightHalf ;
457+ } else if ( element instanceof RenderableLine ) {
412458
413- const node = object . node ;
414- node . setAttribute ( 'transform' , 'translate(' + x + ',' + y + ')' ) ;
459+ _v1 = element . v1 ; _v2 = element . v2 ;
415460
416- _svg . appendChild ( node ) ;
461+ _v1 . positionScreen . x *= _svgWidthHalf ; _v1 . positionScreen . y *= - _svgHeightHalf ;
462+ _v2 . positionScreen . x *= _svgWidthHalf ; _v2 . positionScreen . y *= - _svgHeightHalf ;
463+
464+ _elemBox . setFromPoints ( [ _v1 . positionScreen , _v2 . positionScreen ] ) ;
465+
466+ if ( _clipBox . intersectsBox ( _elemBox ) === true ) {
467+
468+ renderLine ( _v1 , _v2 , material ) ;
469+
470+ }
471+
472+ } else if ( element instanceof RenderableFace ) {
473+
474+ _v1 = element . v1 ; _v2 = element . v2 ; _v3 = element . v3 ;
475+
476+ _v1 . positionScreen . x *= _svgWidthHalf ; _v1 . positionScreen . y *= - _svgHeightHalf ;
477+ _v2 . positionScreen . x *= _svgWidthHalf ; _v2 . positionScreen . y *= - _svgHeightHalf ;
478+ _v3 . positionScreen . x *= _svgWidthHalf ; _v3 . positionScreen . y *= - _svgHeightHalf ;
479+
480+ if ( this . overdraw > 0 ) {
481+
482+ expand ( _v1 . positionScreen , _v2 . positionScreen , this . overdraw ) ;
483+ expand ( _v2 . positionScreen , _v3 . positionScreen , this . overdraw ) ;
484+ expand ( _v3 . positionScreen , _v1 . positionScreen , this . overdraw ) ;
485+
486+ }
487+
488+ _elemBox . setFromPoints ( [
489+ _v1 . positionScreen ,
490+ _v2 . positionScreen ,
491+ _v3 . positionScreen
492+ ] ) ;
493+
494+ if ( _clipBox . intersectsBox ( _elemBox ) === true ) {
495+
496+ renderFace3 ( _v1 , _v2 , _v3 , element , material ) ;
497+
498+ }
499+
500+ }
417501
418502 }
419503
420- } ) ;
504+ }
505+
506+ flushPath ( ) ; // Flush any remaining paths
421507
422508 } ;
423509
@@ -657,21 +743,71 @@ class SVGRenderer {
657743
658744 function getPathNode ( id ) {
659745
660- if ( _svgPathPool [ id ] == null ) {
746+ let path = _svgPathPool [ id ] ;
661747
662- _svgPathPool [ id ] = document . createElementNS ( 'http://www.w3.org/2000/svg' , 'path' ) ;
748+ if ( path === undefined ) {
749+
750+ path = document . createElementNS ( 'http://www.w3.org/2000/svg' , 'path' ) ;
663751
664752 if ( _quality == 0 ) {
665753
666- _svgPathPool [ id ] . setAttribute ( 'shape-rendering' , 'crispEdges' ) ; //optimizeSpeed
754+ path . setAttribute ( 'shape-rendering' , 'crispEdges' ) ; //optimizeSpeed
667755
668756 }
669757
670- return _svgPathPool [ id ] ;
758+ _svgPathPool [ id ] = path ;
759+
760+ }
761+
762+ return path ;
763+
764+ }
765+
766+ function getSVGObjectData ( id ) {
767+
768+ let svgObject = _svgObjectsPool [ id ] ;
769+
770+ if ( svgObject === undefined ) {
771+
772+ svgObject = {
773+ node : null ,
774+ x : 0 ,
775+ y : 0 ,
776+ z : 0 ,
777+ renderOrder : 0
778+ } ;
779+
780+ _svgObjectsPool [ id ] = svgObject ;
671781
672782 }
673783
674- return _svgPathPool [ id ] ;
784+ return svgObject ;
785+
786+ }
787+
788+ function getRenderItem ( id , type , data , material ) {
789+
790+ let item = _renderListPool [ id ] ;
791+
792+ if ( item === undefined ) {
793+
794+ item = {
795+ type : type ,
796+ data : data ,
797+ material : material
798+ } ;
799+
800+ _renderListPool [ id ] = item ;
801+
802+ return item ;
803+
804+ }
805+
806+ item . type = type ;
807+ item . data = data ;
808+ item . material = material ;
809+
810+ return item ;
675811
676812 }
677813
0 commit comments