diff --git a/examples/js/loaders/SVGLoader.js b/examples/js/loaders/SVGLoader.js index b0a287b170f3a0..9c38fbb24d5405 100644 --- a/examples/js/loaders/SVGLoader.js +++ b/examples/js/loaders/SVGLoader.js @@ -59,6 +59,8 @@ THREE.SVGLoader.prototype = Object.assign( Object.create( THREE.Loader.prototype var transform = getNodeTransform( node ); + var traverseChildNodes = true; + var path = null; switch ( node.nodeName ) { @@ -109,6 +111,24 @@ THREE.SVGLoader.prototype = Object.assign( Object.create( THREE.Loader.prototype path = parseLineNode( node ); break; + case 'defs': + traverseChildNodes = false; + break; + + case 'use': + style = parseStyle( node, style ); + var usedNodeId = node.href.baseVal.substring( 1 ); + var usedNode = node.viewportElement.getElementById( usedNodeId ); + if ( usedNode ) { + + parseNode( usedNode, style ); + + } + else console.warn( "SVGLoader: 'use node' references non-existent node id: " + usedNodeId ); + break; + + break; + default: // console.log( node ); @@ -130,11 +150,15 @@ THREE.SVGLoader.prototype = Object.assign( Object.create( THREE.Loader.prototype } - var nodes = node.childNodes; + if ( traverseChildNodes ) { + + var nodes = node.childNodes; - for ( var i = 0; i < nodes.length; i ++ ) { + for ( var i = 0; i < nodes.length; i ++ ) { - parseNode( nodes[ i ], style ); + parseNode( nodes[ i ], style ); + + } } @@ -641,13 +665,13 @@ THREE.SVGLoader.prototype = Object.assign( Object.create( THREE.Loader.prototype rx = Math.abs( rx ); ry = Math.abs( ry ); - // Compute (x1′, y1′) + // Compute (x1', y1') var dx2 = ( start.x - end.x ) / 2.0; var dy2 = ( start.y - end.y ) / 2.0; var x1p = Math.cos( x_axis_rotation ) * dx2 + Math.sin( x_axis_rotation ) * dy2; var y1p = - Math.sin( x_axis_rotation ) * dx2 + Math.cos( x_axis_rotation ) * dy2; - // Compute (cx′, cy′) + // Compute (cx', cy') var rxs = rx * rx; var rys = ry * ry; var x1ps = x1p * x1p; @@ -674,7 +698,7 @@ THREE.SVGLoader.prototype = Object.assign( Object.create( THREE.Loader.prototype var cxp = q * rx * y1p / ry; var cyp = - q * ry * x1p / rx; - // Step 3: Compute (cx, cy) from (cx′, cy′) + // Step 3: Compute (cx, cy) from (cx', cy') var cx = Math.cos( x_axis_rotation ) * cxp - Math.sin( x_axis_rotation ) * cyp + ( start.x + end.x ) / 2; var cy = Math.sin( x_axis_rotation ) * cxp + Math.cos( x_axis_rotation ) * cyp + ( start.y + end.y ) / 2; @@ -887,6 +911,8 @@ THREE.SVGLoader.prototype = Object.assign( Object.create( THREE.Loader.prototype if ( adjustFunction === undefined ) adjustFunction = function copy( v ) { + if ( v.startsWith( 'url' ) ) console.warn( "SVGLoader: url access in attributes is not implemented." ); + return v; }; @@ -1069,7 +1095,7 @@ THREE.SVGLoader.prototype = Object.assign( Object.create( THREE.Loader.prototype function getNodeTransform( node ) { - if ( ! node.hasAttribute( 'transform' ) ) { + if ( ! ( node.hasAttribute( 'transform' ) || ( node.nodeName === 'use' && ( node.hasAttribute( 'x' ) || node.hasAttribute( 'y' ) ) ) ) ) { return null; @@ -1094,142 +1120,156 @@ THREE.SVGLoader.prototype = Object.assign( Object.create( THREE.Loader.prototype var transform = new THREE.Matrix3(); var currentTransform = tempTransform0; - var transformsTexts = node.getAttribute( 'transform' ).split( ')' ); - for ( var tIndex = transformsTexts.length - 1; tIndex >= 0; tIndex -- ) { + if ( node.nodeName === 'use' && ( node.hasAttribute( 'x' ) || node.hasAttribute( 'y' ) ) ) { + + var tx = parseFloatWithUnits( node.getAttribute( 'x' ) ); + var ty = parseFloatWithUnits( node.getAttribute( 'y' ) ); - var transformText = transformsTexts[ tIndex ].trim(); + transform.translate( tx, ty ); - if ( transformText === '' ) continue; + } - var openParPos = transformText.indexOf( '(' ); - var closeParPos = transformText.length; + if ( node.hasAttribute( 'transform' ) ) { - if ( openParPos > 0 && openParPos < closeParPos ) { + var transformsTexts = node.getAttribute( 'transform' ).split( ')' ); - var transformType = transformText.substr( 0, openParPos ); + for ( var tIndex = transformsTexts.length - 1; tIndex >= 0; tIndex -- ) { - var array = parseFloats( transformText.substr( openParPos + 1, closeParPos - openParPos - 1 ) ); + var transformText = transformsTexts[ tIndex ].trim(); - currentTransform.identity(); + if ( transformText === '' ) continue; + + var openParPos = transformText.indexOf( '(' ); + var closeParPos = transformText.length; + + if ( openParPos > 0 && openParPos < closeParPos ) { + + var transformType = transformText.substr( 0, openParPos ); + + var array = parseFloats( transformText.substr( openParPos + 1, closeParPos - openParPos - 1 ) ); + + currentTransform.identity(); + + switch ( transformType ) { - switch ( transformType ) { + case "translate": - case "translate": + if ( array.length >= 1 ) { - if ( array.length >= 1 ) { + var tx = array[ 0 ]; + var ty = tx; - var tx = array[ 0 ]; - var ty = tx; + if ( array.length >= 2 ) { - if ( array.length >= 2 ) { + ty = array[ 1 ]; - ty = array[ 1 ]; + } + + currentTransform.translate( tx, ty ); } - currentTransform.translate( tx, ty ); + break; - } + case "rotate": - break; + if ( array.length >= 1 ) { - case "rotate": + var angle = 0; + var cx = 0; + var cy = 0; - if ( array.length >= 1 ) { + // Angle + angle = - array[ 0 ] * Math.PI / 180; - var angle = 0; - var cx = 0; - var cy = 0; + if ( array.length >= 3 ) { - // Angle - angle = - array[ 0 ] * Math.PI / 180; + // Center x, y + cx = array[ 1 ]; + cy = array[ 2 ]; - if ( array.length >= 3 ) { + } - // Center x, y - cx = array[ 1 ]; - cy = array[ 2 ]; + // Rotate around center (cx, cy) + tempTransform1.identity().translate( - cx, - cy ); + tempTransform2.identity().rotate( angle ); + tempTransform3.multiplyMatrices( tempTransform2, tempTransform1 ); + tempTransform1.identity().translate( cx, cy ); + currentTransform.multiplyMatrices( tempTransform1, tempTransform3 ); } - // Rotate around center (cx, cy) - tempTransform1.identity().translate( - cx, - cy ); - tempTransform2.identity().rotate( angle ); - tempTransform3.multiplyMatrices( tempTransform2, tempTransform1 ); - tempTransform1.identity().translate( cx, cy ); - currentTransform.multiplyMatrices( tempTransform1, tempTransform3 ); + break; - } + case "scale": - break; + if ( array.length >= 1 ) { - case "scale": + var scaleX = array[ 0 ]; + var scaleY = scaleX; - if ( array.length >= 1 ) { + if ( array.length >= 2 ) { - var scaleX = array[ 0 ]; - var scaleY = scaleX; + scaleY = array[ 1 ]; - if ( array.length >= 2 ) { + } - scaleY = array[ 1 ]; + currentTransform.scale( scaleX, scaleY ); } - currentTransform.scale( scaleX, scaleY ); + break; - } + case "skewX": - break; + if ( array.length === 1 ) { - case "skewX": + currentTransform.set( + 1, Math.tan( array[ 0 ] * Math.PI / 180 ), 0, + 0, 1, 0, + 0, 0, 1 + ); - if ( array.length === 1 ) { + } - currentTransform.set( - 1, Math.tan( array[ 0 ] * Math.PI / 180 ), 0, - 0, 1, 0, - 0, 0, 1 - ); + break; - } + case "skewY": - break; + if ( array.length === 1 ) { - case "skewY": + currentTransform.set( + 1, 0, 0, + Math.tan( array[ 0 ] * Math.PI / 180 ), 1, 0, + 0, 0, 1 + ); - if ( array.length === 1 ) { + } - currentTransform.set( - 1, 0, 0, - Math.tan( array[ 0 ] * Math.PI / 180 ), 1, 0, - 0, 0, 1 - ); + break; - } + case "matrix": - break; + if ( array.length === 6 ) { - case "matrix": + currentTransform.set( + array[ 0 ], array[ 2 ], array[ 4 ], + array[ 1 ], array[ 3 ], array[ 5 ], + 0, 0, 1 + ); - if ( array.length === 6 ) { + } - currentTransform.set( - array[ 0 ], array[ 2 ], array[ 4 ], - array[ 1 ], array[ 3 ], array[ 5 ], - 0, 0, 1 - ); + break; - } - - break; + } } - } + transform.premultiply( currentTransform ); - transform.premultiply( currentTransform ); + } } diff --git a/examples/jsm/loaders/SVGLoader.js b/examples/jsm/loaders/SVGLoader.js index a14fc691318f1e..03248a4fdf7f2a 100644 --- a/examples/jsm/loaders/SVGLoader.js +++ b/examples/jsm/loaders/SVGLoader.js @@ -69,6 +69,8 @@ SVGLoader.prototype = Object.assign( Object.create( Loader.prototype ), { var transform = getNodeTransform( node ); + var traverseChildNodes = true; + var path = null; switch ( node.nodeName ) { @@ -119,6 +121,24 @@ SVGLoader.prototype = Object.assign( Object.create( Loader.prototype ), { path = parseLineNode( node ); break; + case 'defs': + traverseChildNodes = false; + break; + + case 'use': + style = parseStyle( node, style ); + var usedNodeId = node.href.baseVal.substring( 1 ); + var usedNode = node.viewportElement.getElementById( usedNodeId ); + if ( usedNode ) { + + parseNode( usedNode, style ); + + } + else console.warn( "SVGLoader: 'use node' references non-existent node id: " + usedNodeId ); + break; + + break; + default: // console.log( node ); @@ -140,11 +160,15 @@ SVGLoader.prototype = Object.assign( Object.create( Loader.prototype ), { } - var nodes = node.childNodes; + if ( traverseChildNodes ) { + + var nodes = node.childNodes; - for ( var i = 0; i < nodes.length; i ++ ) { + for ( var i = 0; i < nodes.length; i ++ ) { - parseNode( nodes[ i ], style ); + parseNode( nodes[ i ], style ); + + } } @@ -651,13 +675,13 @@ SVGLoader.prototype = Object.assign( Object.create( Loader.prototype ), { rx = Math.abs( rx ); ry = Math.abs( ry ); - // Compute (x1′, y1′) + // Compute (x1', y1') var dx2 = ( start.x - end.x ) / 2.0; var dy2 = ( start.y - end.y ) / 2.0; var x1p = Math.cos( x_axis_rotation ) * dx2 + Math.sin( x_axis_rotation ) * dy2; var y1p = - Math.sin( x_axis_rotation ) * dx2 + Math.cos( x_axis_rotation ) * dy2; - // Compute (cx′, cy′) + // Compute (cx', cy') var rxs = rx * rx; var rys = ry * ry; var x1ps = x1p * x1p; @@ -684,7 +708,7 @@ SVGLoader.prototype = Object.assign( Object.create( Loader.prototype ), { var cxp = q * rx * y1p / ry; var cyp = - q * ry * x1p / rx; - // Step 3: Compute (cx, cy) from (cx′, cy′) + // Step 3: Compute (cx, cy) from (cx', cy') var cx = Math.cos( x_axis_rotation ) * cxp - Math.sin( x_axis_rotation ) * cyp + ( start.x + end.x ) / 2; var cy = Math.sin( x_axis_rotation ) * cxp + Math.cos( x_axis_rotation ) * cyp + ( start.y + end.y ) / 2; @@ -897,6 +921,8 @@ SVGLoader.prototype = Object.assign( Object.create( Loader.prototype ), { if ( adjustFunction === undefined ) adjustFunction = function copy( v ) { + if ( v.startsWith( 'url' ) ) console.warn( "SVGLoader: url access in attributes is not implemented." ); + return v; }; @@ -1079,7 +1105,7 @@ SVGLoader.prototype = Object.assign( Object.create( Loader.prototype ), { function getNodeTransform( node ) { - if ( ! node.hasAttribute( 'transform' ) ) { + if ( ! ( node.hasAttribute( 'transform' ) || ( node.nodeName === 'use' && ( node.hasAttribute( 'x' ) || node.hasAttribute( 'y' ) ) ) ) ) { return null; @@ -1104,142 +1130,156 @@ SVGLoader.prototype = Object.assign( Object.create( Loader.prototype ), { var transform = new Matrix3(); var currentTransform = tempTransform0; - var transformsTexts = node.getAttribute( 'transform' ).split( ')' ); - for ( var tIndex = transformsTexts.length - 1; tIndex >= 0; tIndex -- ) { + if ( node.nodeName === 'use' && ( node.hasAttribute( 'x' ) || node.hasAttribute( 'y' ) ) ) { + + var tx = parseFloatWithUnits( node.getAttribute( 'x' ) ); + var ty = parseFloatWithUnits( node.getAttribute( 'y' ) ); - var transformText = transformsTexts[ tIndex ].trim(); + transform.translate( tx, ty ); - if ( transformText === '' ) continue; + } - var openParPos = transformText.indexOf( '(' ); - var closeParPos = transformText.length; + if ( node.hasAttribute( 'transform' ) ) { - if ( openParPos > 0 && openParPos < closeParPos ) { + var transformsTexts = node.getAttribute( 'transform' ).split( ')' ); - var transformType = transformText.substr( 0, openParPos ); + for ( var tIndex = transformsTexts.length - 1; tIndex >= 0; tIndex -- ) { - var array = parseFloats( transformText.substr( openParPos + 1, closeParPos - openParPos - 1 ) ); + var transformText = transformsTexts[ tIndex ].trim(); - currentTransform.identity(); + if ( transformText === '' ) continue; + + var openParPos = transformText.indexOf( '(' ); + var closeParPos = transformText.length; + + if ( openParPos > 0 && openParPos < closeParPos ) { + + var transformType = transformText.substr( 0, openParPos ); + + var array = parseFloats( transformText.substr( openParPos + 1, closeParPos - openParPos - 1 ) ); + + currentTransform.identity(); + + switch ( transformType ) { - switch ( transformType ) { + case "translate": - case "translate": + if ( array.length >= 1 ) { - if ( array.length >= 1 ) { + var tx = array[ 0 ]; + var ty = tx; - var tx = array[ 0 ]; - var ty = tx; + if ( array.length >= 2 ) { - if ( array.length >= 2 ) { + ty = array[ 1 ]; - ty = array[ 1 ]; + } + + currentTransform.translate( tx, ty ); } - currentTransform.translate( tx, ty ); + break; - } + case "rotate": - break; + if ( array.length >= 1 ) { - case "rotate": + var angle = 0; + var cx = 0; + var cy = 0; - if ( array.length >= 1 ) { + // Angle + angle = - array[ 0 ] * Math.PI / 180; - var angle = 0; - var cx = 0; - var cy = 0; + if ( array.length >= 3 ) { - // Angle - angle = - array[ 0 ] * Math.PI / 180; + // Center x, y + cx = array[ 1 ]; + cy = array[ 2 ]; - if ( array.length >= 3 ) { + } - // Center x, y - cx = array[ 1 ]; - cy = array[ 2 ]; + // Rotate around center (cx, cy) + tempTransform1.identity().translate( - cx, - cy ); + tempTransform2.identity().rotate( angle ); + tempTransform3.multiplyMatrices( tempTransform2, tempTransform1 ); + tempTransform1.identity().translate( cx, cy ); + currentTransform.multiplyMatrices( tempTransform1, tempTransform3 ); } - // Rotate around center (cx, cy) - tempTransform1.identity().translate( - cx, - cy ); - tempTransform2.identity().rotate( angle ); - tempTransform3.multiplyMatrices( tempTransform2, tempTransform1 ); - tempTransform1.identity().translate( cx, cy ); - currentTransform.multiplyMatrices( tempTransform1, tempTransform3 ); + break; - } + case "scale": - break; + if ( array.length >= 1 ) { - case "scale": + var scaleX = array[ 0 ]; + var scaleY = scaleX; - if ( array.length >= 1 ) { + if ( array.length >= 2 ) { - var scaleX = array[ 0 ]; - var scaleY = scaleX; + scaleY = array[ 1 ]; - if ( array.length >= 2 ) { + } - scaleY = array[ 1 ]; + currentTransform.scale( scaleX, scaleY ); } - currentTransform.scale( scaleX, scaleY ); + break; - } + case "skewX": - break; + if ( array.length === 1 ) { - case "skewX": + currentTransform.set( + 1, Math.tan( array[ 0 ] * Math.PI / 180 ), 0, + 0, 1, 0, + 0, 0, 1 + ); - if ( array.length === 1 ) { + } - currentTransform.set( - 1, Math.tan( array[ 0 ] * Math.PI / 180 ), 0, - 0, 1, 0, - 0, 0, 1 - ); + break; - } + case "skewY": - break; + if ( array.length === 1 ) { - case "skewY": + currentTransform.set( + 1, 0, 0, + Math.tan( array[ 0 ] * Math.PI / 180 ), 1, 0, + 0, 0, 1 + ); - if ( array.length === 1 ) { + } - currentTransform.set( - 1, 0, 0, - Math.tan( array[ 0 ] * Math.PI / 180 ), 1, 0, - 0, 0, 1 - ); + break; - } + case "matrix": - break; + if ( array.length === 6 ) { - case "matrix": + currentTransform.set( + array[ 0 ], array[ 2 ], array[ 4 ], + array[ 1 ], array[ 3 ], array[ 5 ], + 0, 0, 1 + ); - if ( array.length === 6 ) { + } - currentTransform.set( - array[ 0 ], array[ 2 ], array[ 4 ], - array[ 1 ], array[ 3 ], array[ 5 ], - 0, 0, 1 - ); + break; - } - - break; + } } - } + transform.premultiply( currentTransform ); - transform.premultiply( currentTransform ); + } } diff --git a/examples/models/svg/tests/testDefs/Svg-defs-license.txt b/examples/models/svg/tests/testDefs/Svg-defs-license.txt new file mode 100644 index 00000000000000..1ee5e52b8a6570 --- /dev/null +++ b/examples/models/svg/tests/testDefs/Svg-defs-license.txt @@ -0,0 +1,6 @@ +File: Svg-defs.svg +Link: https://commons.wikimedia.org/wiki/File:Svg-defs.svg +License: CC-BY-SA 2.5 +License link: https://creativecommons.org/licenses/by-sa/3.0/deed.en + +The file Svg-defs2.svg is a derived work of the same file. diff --git a/examples/models/svg/tests/testDefs/Svg-defs.svg b/examples/models/svg/tests/testDefs/Svg-defs.svg new file mode 100644 index 00000000000000..4f89a249ca43d8 --- /dev/null +++ b/examples/models/svg/tests/testDefs/Svg-defs.svg @@ -0,0 +1,13 @@ + + + + + ... + + + + + + diff --git a/examples/models/svg/tests/testDefs/Svg-defs2.svg b/examples/models/svg/tests/testDefs/Svg-defs2.svg new file mode 100644 index 00000000000000..4118089dff5c37 --- /dev/null +++ b/examples/models/svg/tests/testDefs/Svg-defs2.svg @@ -0,0 +1,73 @@ + + + + + + + + image/svg+xml + + + + + + ... + + + + + + diff --git a/examples/models/svg/tests/testDefs/Wave-defs-license.txt b/examples/models/svg/tests/testDefs/Wave-defs-license.txt new file mode 100644 index 00000000000000..1fa4befd43f7d8 --- /dev/null +++ b/examples/models/svg/tests/testDefs/Wave-defs-license.txt @@ -0,0 +1,5 @@ +File: Wave-defs.svg +Original file name: Växelförlopp-defs.svg +Link: https://commons.wikimedia.org/wiki/File:V%C3%A4xelf%C3%B6rlopp-defs.svg +License: This file is licensed under the Creative Commons Attribution-Share Alike 3.0 Unported license. +License link: https://creativecommons.org/licenses/by-sa/3.0/deed.en diff --git a/examples/models/svg/tests/testDefs/Wave-defs.svg b/examples/models/svg/tests/testDefs/Wave-defs.svg new file mode 100644 index 00000000000000..725b9eb78ac08f --- /dev/null +++ b/examples/models/svg/tests/testDefs/Wave-defs.svg @@ -0,0 +1,376 @@ + + + + + + image/svg+xml + + + + + + + + gnuplot + Produced by GNUPLOT 4.4 patchlevel 4 + + + + + + + + + + + + + + + + + + + + Plot_1 + + + + + + + + + + + + + + + + + +   + + + + + + + + + + + + + + + + + + + + + + + + + + ^ + + + + diff --git a/examples/models/svg/tests/testDefs/defs4.svg b/examples/models/svg/tests/testDefs/defs4.svg new file mode 100644 index 00000000000000..127d95154ff0a9 --- /dev/null +++ b/examples/models/svg/tests/testDefs/defs4.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/webgl_loader_svg.html b/examples/webgl_loader_svg.html index 42028222daa44b..98d518173d5ca1 100644 --- a/examples/webgl_loader_svg.html +++ b/examples/webgl_loader_svg.html @@ -100,7 +100,12 @@ "Test 6": 'models/svg/tests/6.svg', "Test 7": 'models/svg/tests/7.svg', "Test 8": 'models/svg/tests/8.svg', - "Units": 'models/svg/tests/units.svg' + "Units": 'models/svg/tests/units.svg', + "Defs": 'models/svg/tests/testDefs/Svg-defs.svg', + "Defs2": 'models/svg/tests/testDefs/Svg-defs2.svg', + "Defs3": 'models/svg/tests/testDefs/Wave-defs.svg', + "Defs4": 'models/svg/tests/testDefs/defs4.svg' + } ).name( 'SVG File' ).onChange( update );