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 @@
+
+
+
+
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 @@
+
+
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 );