Skip to content

Commit 43324e6

Browse files
authored
SVGRenderer: Add depth sorting for Sprites and SVGObjects (#32212)
* SVGRenderer: Implemented Sprite and SVGObject sorting. * Added OrbitControls to svg_sandbox. * Updated screenshot.
1 parent b03e036 commit 43324e6

File tree

4 files changed

+222
-61
lines changed

4 files changed

+222
-61
lines changed

examples/jsm/renderers/Projector.js

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -577,7 +577,7 @@ class Projector {
577577

578578
if ( sortObjects === true ) {
579579

580-
_renderData.objects.sort( painterSort );
580+
painterSortStable( _renderData.objects, 0, _renderData.objects.length );
581581

582582
}
583583

@@ -843,7 +843,7 @@ class Projector {
843843

844844
if ( sortElements === true ) {
845845

846-
_renderData.elements.sort( painterSort );
846+
painterSortStable( _renderData.elements, 0, _renderData.elements.length );
847847

848848
}
849849

@@ -987,6 +987,29 @@ class Projector {
987987

988988
}
989989

990+
function painterSortStable( array, start, length ) {
991+
992+
// A stable insertion sort for sorting render items
993+
// This avoids the GC overhead of Array.prototype.sort()
994+
995+
for ( let i = start + 1; i < start + length; i ++ ) {
996+
997+
const item = array[ i ];
998+
let j = i - 1;
999+
1000+
while ( j >= start && painterSort( array[ j ], item ) > 0 ) {
1001+
1002+
array[ j + 1 ] = array[ j ];
1003+
j --;
1004+
1005+
}
1006+
1007+
array[ j + 1 ] = item;
1008+
1009+
}
1010+
1011+
}
1012+
9901013
// Sutherland-Hodgman triangle clipping in homogeneous clip space
9911014
// Returns count of vertices in clipped polygon (0 if completely clipped, 3+ if partially clipped)
9921015
// Result vertices are in _clipInput array

examples/jsm/renderers/SVGRenderer.js

Lines changed: 190 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -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

-7.03 KB
Loading

0 commit comments

Comments
 (0)