diff --git a/editor/js/Loader.js b/editor/js/Loader.js index 3e16e4a9e8c6e4..4949984079ccce 100644 --- a/editor/js/Loader.js +++ b/editor/js/Loader.js @@ -704,25 +704,54 @@ function Loader( editor ) { group.scale.multiplyScalar( 0.1 ); group.scale.y *= - 1; + let renderOrder = 0; + for ( let i = 0; i < paths.length; i ++ ) { const path = paths[ i ]; - const material = new THREE.MeshBasicMaterial( { - color: path.color, - depthWrite: false - } ); + // fill + + const fillMaterial = SVGLoader.createFillMaterial( path ); + + if ( fillMaterial ) { + + const shapes = SVGLoader.createShapes( path ); - const shapes = SVGLoader.createShapes( path ); + for ( let j = 0; j < shapes.length; j ++ ) { - for ( let j = 0; j < shapes.length; j ++ ) { + const shape = shapes[ j ]; - const shape = shapes[ j ]; + const geometry = new THREE.ShapeGeometry( shape ); + const mesh = new THREE.Mesh( geometry, fillMaterial ); + mesh.renderOrder = renderOrder ++; + + group.add( mesh ); + + } + + } - const geometry = new THREE.ShapeGeometry( shape ); - const mesh = new THREE.Mesh( geometry, material ); + // stroke - group.add( mesh ); + const strokeMaterial = SVGLoader.createStrokeMaterial( path ); + + if ( strokeMaterial ) { + + for ( const subPath of path.subPaths ) { + + const geometry = SVGLoader.pointsToStroke( subPath.getPoints(), path.userData.style ); + + if ( geometry ) { + + const mesh = new THREE.Mesh( geometry, strokeMaterial ); + mesh.renderOrder = renderOrder ++; + + group.add( mesh ); + + } + + } } diff --git a/examples/jsm/loaders/SVGLoader.js b/examples/jsm/loaders/SVGLoader.js index 98e2c33dd3756f..38306625adbbd7 100644 --- a/examples/jsm/loaders/SVGLoader.js +++ b/examples/jsm/loaders/SVGLoader.js @@ -2663,6 +2663,22 @@ class SVGLoader extends Loader { outerPoint.copy( tempV2_5 ).add( currentPoint ); innerPoint.add( currentPoint ); + // in-loop fold detection to mitigate #25326 + if ( innerSideModified ) { + + // when the second triangle's signed area would flip, snap innerPoint to the previous inner-side vertex + + const refPt = joinIsOnLeftSide ? lastPointR : lastPointL; + const foldCross = ( outerPoint.x - refPt.x ) * ( innerPoint.y - refPt.y ) + - ( outerPoint.y - refPt.y ) * ( innerPoint.x - refPt.x ); + if ( ( joinIsOnLeftSide && foldCross < 0 ) || ( ! joinIsOnLeftSide && foldCross > 0 ) ) { + + innerPoint.copy( refPt ); + + } + + } + isMiter = false; if ( innerSideModified ) { @@ -2941,6 +2957,32 @@ class SVGLoader extends Loader { } + // Second fix for #25326: Scan for reamining flipped (CW) triangles and collapse them to + // degenerated ones. This is safe and leaves no "holes" in the stroke because the flipped + // triangle's area is covered by neighbouring (CCW) triangles. + + if ( vertices ) { + + const tri = [ new Vector2(), new Vector2(), new Vector2() ]; + const startFloat = vertexOffset * 3; + + for ( let t = startFloat; t < currentCoordinate; t += 9 ) { + + tri[ 0 ].set( vertices[ t ], vertices[ t + 1 ] ); + tri[ 1 ].set( vertices[ t + 3 ], vertices[ t + 4 ] ); + tri[ 2 ].set( vertices[ t + 6 ], vertices[ t + 7 ] ); + + if ( ShapeUtils.area( tri ) < 0 ) { + + vertices[ t + 3 ] = tri[ 0 ].x; + vertices[ t + 4 ] = tri[ 0 ].y; + + } + + } + + } + return numVertices; // -- End of algorithm diff --git a/examples/models/svg/tests/wideStroke.svg b/examples/models/svg/tests/wideStroke.svg new file mode 100644 index 00000000000000..6b774f0b7abd22 --- /dev/null +++ b/examples/models/svg/tests/wideStroke.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/examples/screenshots/webgl_loader_ldraw.jpg b/examples/screenshots/webgl_loader_ldraw.jpg index 84e99d0b1fa39a..c44637b5bbfd5b 100644 Binary files a/examples/screenshots/webgl_loader_ldraw.jpg and b/examples/screenshots/webgl_loader_ldraw.jpg differ diff --git a/examples/webgl_loader_svg.html b/examples/webgl_loader_svg.html index aec169ee119911..4012e0530239f0 100644 --- a/examples/webgl_loader_svg.html +++ b/examples/webgl_loader_svg.html @@ -122,6 +122,7 @@ 'emptyPath': 'models/svg/emptyPath.svg', 'emoji': 'models/svg/emoji.svg', 'blueprint': 'models/svg/blueprint.svg', + 'wideStroke': 'models/svg/tests/wideStroke.svg', } ).name( 'SVG File' ).onChange( update ); diff --git a/src/renderers/WebGLRenderer.js b/src/renderers/WebGLRenderer.js index ad830e1b2daf85..a63971cd0bce20 100644 --- a/src/renderers/WebGLRenderer.js +++ b/src/renderers/WebGLRenderer.js @@ -544,7 +544,7 @@ class WebGLRenderer { if ( _outputBufferType !== UnsignedByteType ) { - output = new WebGLOutput( _outputBufferType, canvas.width, canvas.height, depth, stencil ); + output = new WebGLOutput( _outputBufferType, canvas.width, canvas.height, antialias, depth, stencil ); } diff --git a/src/renderers/webgl/WebGLOutput.js b/src/renderers/webgl/WebGLOutput.js index 228d1412c18633..bca2356e1a8f41 100644 --- a/src/renderers/webgl/WebGLOutput.js +++ b/src/renderers/webgl/WebGLOutput.js @@ -29,13 +29,14 @@ const toneMappingMap = { [ CustomToneMapping ]: 'CUSTOM_TONE_MAPPING' }; -function WebGLOutput( type, width, height, depth, stencil ) { +function WebGLOutput( type, width, height, antialias, depth, stencil ) { // render targets for scene and post-processing const targetA = new WebGLRenderTarget( width, height, { type: type, depthBuffer: depth, stencilBuffer: stencil, + samples: antialias ? 4 : 0, depthTexture: depth ? new DepthTexture( width, height ) : undefined } );