|
|
@@ -88,6 +88,8 @@ class SVGRenderer {
|
|
|
|
|
|
_svgNode,
|
|
|
_pathCount = 0,
|
|
|
+ _svgObjectCount = 0,
|
|
|
+ _renderListCount = 0,
|
|
|
|
|
|
_precision = null,
|
|
|
_quality = 1,
|
|
|
@@ -114,6 +116,8 @@ class SVGRenderer {
|
|
|
_viewProjectionMatrix = new Matrix4(),
|
|
|
|
|
|
_svgPathPool = [],
|
|
|
+ _svgObjectsPool = [],
|
|
|
+ _renderListPool = [],
|
|
|
|
|
|
_projector = new Projector(),
|
|
|
_svg = document.createElementNS( 'http://www.w3.org/2000/svg', 'svg' );
|
|
|
@@ -276,6 +280,49 @@ class SVGRenderer {
|
|
|
|
|
|
}
|
|
|
|
|
|
+ function renderSort( a, b ) {
|
|
|
+
|
|
|
+ const aOrder = a.data.renderOrder !== undefined ? a.data.renderOrder : 0;
|
|
|
+ const bOrder = b.data.renderOrder !== undefined ? b.data.renderOrder : 0;
|
|
|
+
|
|
|
+ if ( aOrder !== bOrder ) {
|
|
|
+
|
|
|
+ return aOrder - bOrder;
|
|
|
+
|
|
|
+ } else {
|
|
|
+
|
|
|
+ const aZ = a.data.z !== undefined ? a.data.z : 0;
|
|
|
+ const bZ = b.data.z !== undefined ? b.data.z : 0;
|
|
|
+
|
|
|
+ return bZ - aZ; // Painter's algorithm: far to near
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ function arraySortStable( array, start, length ) {
|
|
|
+
|
|
|
+ // A stable insertion sort for sorting the render list
|
|
|
+ // This avoids the GC overhead of Array.prototype.sort()
|
|
|
+
|
|
|
+ for ( let i = start + 1; i < start + length; i ++ ) {
|
|
|
+
|
|
|
+ const item = array[ i ];
|
|
|
+ let j = i - 1;
|
|
|
+
|
|
|
+ while ( j >= start && renderSort( array[ j ], item ) > 0 ) {
|
|
|
+
|
|
|
+ array[ j + 1 ] = array[ j ];
|
|
|
+ j --;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ array[ j + 1 ] = item;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* Performs a manual clear with the defined clear color.
|
|
|
*/
|
|
|
@@ -328,10 +375,7 @@ class SVGRenderer {
|
|
|
|
|
|
calculateLights( _lights );
|
|
|
|
|
|
- // reset accumulated path
|
|
|
-
|
|
|
- _currentPath = '';
|
|
|
- _currentStyle = '';
|
|
|
+ _renderListCount = 0;
|
|
|
|
|
|
for ( let e = 0, el = _elements.length; e < el; e ++ ) {
|
|
|
|
|
|
@@ -340,84 +384,126 @@ class SVGRenderer {
|
|
|
|
|
|
if ( material === undefined || material.opacity === 0 ) continue;
|
|
|
|
|
|
- _elemBox.makeEmpty();
|
|
|
+ getRenderItem( _renderListCount ++, 'element', element, material );
|
|
|
|
|
|
- if ( element instanceof RenderableSprite ) {
|
|
|
+ }
|
|
|
|
|
|
- _v1 = element;
|
|
|
- _v1.x *= _svgWidthHalf; _v1.y *= - _svgHeightHalf;
|
|
|
+ _svgObjectCount = 0;
|
|
|
|
|
|
- renderSprite( _v1, element, material );
|
|
|
+ scene.traverseVisible( function ( object ) {
|
|
|
|
|
|
- } else if ( element instanceof RenderableLine ) {
|
|
|
+ if ( object.isSVGObject ) {
|
|
|
|
|
|
- _v1 = element.v1; _v2 = element.v2;
|
|
|
+ _vector3.setFromMatrixPosition( object.matrixWorld );
|
|
|
+ _vector3.applyMatrix4( _viewProjectionMatrix );
|
|
|
|
|
|
- _v1.positionScreen.x *= _svgWidthHalf; _v1.positionScreen.y *= - _svgHeightHalf;
|
|
|
- _v2.positionScreen.x *= _svgWidthHalf; _v2.positionScreen.y *= - _svgHeightHalf;
|
|
|
+ if ( _vector3.z < - 1 || _vector3.z > 1 ) return;
|
|
|
|
|
|
- _elemBox.setFromPoints( [ _v1.positionScreen, _v2.positionScreen ] );
|
|
|
+ const x = _vector3.x * _svgWidthHalf;
|
|
|
+ const y = - _vector3.y * _svgHeightHalf;
|
|
|
|
|
|
- if ( _clipBox.intersectsBox( _elemBox ) === true ) {
|
|
|
+ const svgObject = getSVGObjectData( _svgObjectCount ++ );
|
|
|
|
|
|
- renderLine( _v1, _v2, material );
|
|
|
+ svgObject.node = object.node;
|
|
|
+ svgObject.x = x;
|
|
|
+ svgObject.y = y;
|
|
|
+ svgObject.z = _vector3.z;
|
|
|
+ svgObject.renderOrder = object.renderOrder;
|
|
|
|
|
|
- }
|
|
|
+ getRenderItem( _renderListCount ++, 'svgObject', svgObject, null );
|
|
|
|
|
|
- } else if ( element instanceof RenderableFace ) {
|
|
|
+ }
|
|
|
|
|
|
- _v1 = element.v1; _v2 = element.v2; _v3 = element.v3;
|
|
|
+ } );
|
|
|
|
|
|
- _v1.positionScreen.x *= _svgWidthHalf; _v1.positionScreen.y *= - _svgHeightHalf;
|
|
|
- _v2.positionScreen.x *= _svgWidthHalf; _v2.positionScreen.y *= - _svgHeightHalf;
|
|
|
- _v3.positionScreen.x *= _svgWidthHalf; _v3.positionScreen.y *= - _svgHeightHalf;
|
|
|
+ if ( this.sortElements ) {
|
|
|
|
|
|
- if ( this.overdraw > 0 ) {
|
|
|
+ arraySortStable( _renderListPool, 0, _renderListCount );
|
|
|
|
|
|
- expand( _v1.positionScreen, _v2.positionScreen, this.overdraw );
|
|
|
- expand( _v2.positionScreen, _v3.positionScreen, this.overdraw );
|
|
|
- expand( _v3.positionScreen, _v1.positionScreen, this.overdraw );
|
|
|
+ }
|
|
|
|
|
|
- }
|
|
|
+ // Reset accumulated path
|
|
|
+ _currentPath = '';
|
|
|
+ _currentStyle = '';
|
|
|
|
|
|
- _elemBox.setFromPoints( [
|
|
|
- _v1.positionScreen,
|
|
|
- _v2.positionScreen,
|
|
|
- _v3.positionScreen
|
|
|
- ] );
|
|
|
+ // Render in sorted order
|
|
|
+ for ( let i = 0; i < _renderListCount; i ++ ) {
|
|
|
|
|
|
- if ( _clipBox.intersectsBox( _elemBox ) === true ) {
|
|
|
+ const item = _renderListPool[ i ];
|
|
|
|
|
|
- renderFace3( _v1, _v2, _v3, element, material );
|
|
|
+ if ( item.type === 'svgObject' ) {
|
|
|
|
|
|
- }
|
|
|
+ flushPath(); // Flush any accumulated paths before inserting SVG node
|
|
|
|
|
|
- }
|
|
|
+ const svgObject = item.data;
|
|
|
+ const node = svgObject.node;
|
|
|
+ node.setAttribute( 'transform', 'translate(' + svgObject.x + ',' + svgObject.y + ')' );
|
|
|
+ _svg.appendChild( node );
|
|
|
|
|
|
- }
|
|
|
+ } else {
|
|
|
|
|
|
- flushPath(); // just to flush last svg:path
|
|
|
+ const element = item.data;
|
|
|
+ const material = item.material;
|
|
|
|
|
|
- scene.traverseVisible( function ( object ) {
|
|
|
+ _elemBox.makeEmpty();
|
|
|
|
|
|
- if ( object.isSVGObject ) {
|
|
|
+ if ( element instanceof RenderableSprite ) {
|
|
|
|
|
|
- _vector3.setFromMatrixPosition( object.matrixWorld );
|
|
|
- _vector3.applyMatrix4( _viewProjectionMatrix );
|
|
|
+ _v1 = element;
|
|
|
+ _v1.x *= _svgWidthHalf; _v1.y *= - _svgHeightHalf;
|
|
|
|
|
|
- if ( _vector3.z < - 1 || _vector3.z > 1 ) return;
|
|
|
+ renderSprite( _v1, element, material );
|
|
|
|
|
|
- const x = _vector3.x * _svgWidthHalf;
|
|
|
- const y = - _vector3.y * _svgHeightHalf;
|
|
|
+ } else if ( element instanceof RenderableLine ) {
|
|
|
|
|
|
- const node = object.node;
|
|
|
- node.setAttribute( 'transform', 'translate(' + x + ',' + y + ')' );
|
|
|
+ _v1 = element.v1; _v2 = element.v2;
|
|
|
|
|
|
- _svg.appendChild( node );
|
|
|
+ _v1.positionScreen.x *= _svgWidthHalf; _v1.positionScreen.y *= - _svgHeightHalf;
|
|
|
+ _v2.positionScreen.x *= _svgWidthHalf; _v2.positionScreen.y *= - _svgHeightHalf;
|
|
|
+
|
|
|
+ _elemBox.setFromPoints( [ _v1.positionScreen, _v2.positionScreen ] );
|
|
|
+
|
|
|
+ if ( _clipBox.intersectsBox( _elemBox ) === true ) {
|
|
|
+
|
|
|
+ renderLine( _v1, _v2, material );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ } else if ( element instanceof RenderableFace ) {
|
|
|
+
|
|
|
+ _v1 = element.v1; _v2 = element.v2; _v3 = element.v3;
|
|
|
+
|
|
|
+ _v1.positionScreen.x *= _svgWidthHalf; _v1.positionScreen.y *= - _svgHeightHalf;
|
|
|
+ _v2.positionScreen.x *= _svgWidthHalf; _v2.positionScreen.y *= - _svgHeightHalf;
|
|
|
+ _v3.positionScreen.x *= _svgWidthHalf; _v3.positionScreen.y *= - _svgHeightHalf;
|
|
|
+
|
|
|
+ if ( this.overdraw > 0 ) {
|
|
|
+
|
|
|
+ expand( _v1.positionScreen, _v2.positionScreen, this.overdraw );
|
|
|
+ expand( _v2.positionScreen, _v3.positionScreen, this.overdraw );
|
|
|
+ expand( _v3.positionScreen, _v1.positionScreen, this.overdraw );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ _elemBox.setFromPoints( [
|
|
|
+ _v1.positionScreen,
|
|
|
+ _v2.positionScreen,
|
|
|
+ _v3.positionScreen
|
|
|
+ ] );
|
|
|
+
|
|
|
+ if ( _clipBox.intersectsBox( _elemBox ) === true ) {
|
|
|
+
|
|
|
+ renderFace3( _v1, _v2, _v3, element, material );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
|
|
|
}
|
|
|
|
|
|
- } );
|
|
|
+ }
|
|
|
+
|
|
|
+ flushPath(); // Flush any remaining paths
|
|
|
|
|
|
};
|
|
|
|
|
|
@@ -657,21 +743,71 @@ class SVGRenderer {
|
|
|
|
|
|
function getPathNode( id ) {
|
|
|
|
|
|
- if ( _svgPathPool[ id ] == null ) {
|
|
|
+ let path = _svgPathPool[ id ];
|
|
|
|
|
|
- _svgPathPool[ id ] = document.createElementNS( 'http://www.w3.org/2000/svg', 'path' );
|
|
|
+ if ( path === undefined ) {
|
|
|
+
|
|
|
+ path = document.createElementNS( 'http://www.w3.org/2000/svg', 'path' );
|
|
|
|
|
|
if ( _quality == 0 ) {
|
|
|
|
|
|
- _svgPathPool[ id ].setAttribute( 'shape-rendering', 'crispEdges' ); //optimizeSpeed
|
|
|
+ path.setAttribute( 'shape-rendering', 'crispEdges' ); //optimizeSpeed
|
|
|
|
|
|
}
|
|
|
|
|
|
- return _svgPathPool[ id ];
|
|
|
+ _svgPathPool[ id ] = path;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ return path;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ function getSVGObjectData( id ) {
|
|
|
+
|
|
|
+ let svgObject = _svgObjectsPool[ id ];
|
|
|
+
|
|
|
+ if ( svgObject === undefined ) {
|
|
|
+
|
|
|
+ svgObject = {
|
|
|
+ node: null,
|
|
|
+ x: 0,
|
|
|
+ y: 0,
|
|
|
+ z: 0,
|
|
|
+ renderOrder: 0
|
|
|
+ };
|
|
|
+
|
|
|
+ _svgObjectsPool[ id ] = svgObject;
|
|
|
|
|
|
}
|
|
|
|
|
|
- return _svgPathPool[ id ];
|
|
|
+ return svgObject;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ function getRenderItem( id, type, data, material ) {
|
|
|
+
|
|
|
+ let item = _renderListPool[ id ];
|
|
|
+
|
|
|
+ if ( item === undefined ) {
|
|
|
+
|
|
|
+ item = {
|
|
|
+ type: type,
|
|
|
+ data: data,
|
|
|
+ material: material
|
|
|
+ };
|
|
|
+
|
|
|
+ _renderListPool[ id ] = item;
|
|
|
+
|
|
|
+ return item;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ item.type = type;
|
|
|
+ item.data = data;
|
|
|
+ item.material = material;
|
|
|
+
|
|
|
+ return item;
|
|
|
|
|
|
}
|
|
|
|