Explorar o código

SVGLoader: Basic gradients support. (#33436)

Michael Herzog hai 1 mes
pai
achega
35ff62b07d

+ 471 - 84
examples/jsm/loaders/SVGLoader.js

@@ -1,6 +1,8 @@
 import {
 import {
 	Box2,
 	Box2,
 	BufferGeometry,
 	BufferGeometry,
+	CanvasTexture,
+	ClampToEdgeWrapping,
 	Color,
 	Color,
 	DoubleSide,
 	DoubleSide,
 	FileLoader,
 	FileLoader,
@@ -8,7 +10,9 @@ import {
 	Loader,
 	Loader,
 	Matrix3,
 	Matrix3,
 	MeshBasicMaterial,
 	MeshBasicMaterial,
+	MirroredRepeatWrapping,
 	Path,
 	Path,
+	RepeatWrapping,
 	Shape,
 	Shape,
 	ShapePath,
 	ShapePath,
 	ShapeUtils,
 	ShapeUtils,
@@ -147,6 +151,12 @@ class SVGLoader extends Loader {
 
 
 			if ( node.nodeType !== 1 ) return;
 			if ( node.nodeType !== 1 ) return;
 
 
+			if ( node.hasAttribute( 'filter' ) ) {
+
+				console.warn( 'THREE.SVGLoader: Filters are not supported.' );
+
+			}
+
 			const transform = getNodeTransform( node );
 			const transform = getNodeTransform( node );
 
 
 			let isDefsNode = false;
 			let isDefsNode = false;
@@ -231,7 +241,7 @@ class SVGLoader extends Loader {
 
 
 			if ( path ) {
 			if ( path ) {
 
 
-				if ( style.fill !== undefined && style.fill !== 'none' ) {
+				if ( style.fill !== undefined && style.fill !== 'none' && ! style.fill.startsWith( 'url' ) ) {
 
 
 					path.color.setStyle( style.fill, COLOR_SPACE_SVG );
 					path.color.setStyle( style.fill, COLOR_SPACE_SVG );
 
 
@@ -244,7 +254,7 @@ class SVGLoader extends Loader {
 				const pathStyle = Object.assign( {}, style );
 				const pathStyle = Object.assign( {}, style );
 				pathStyle.strokeWidth = style.strokeWidth * getTransformScale( currentTransform );
 				pathStyle.strokeWidth = style.strokeWidth * getTransformScale( currentTransform );
 
 
-				path.userData = { node: node, style: pathStyle };
+				path.userData = { node: node, style: pathStyle, transform: currentTransform.clone(), gradients: gradients };
 
 
 			}
 			}
 
 
@@ -1050,6 +1060,146 @@ class SVGLoader extends Loader {
 
 
 		//
 		//
 
 
+		function parseGradients( xml ) {
+
+			const HREF_NS = 'http://www.w3.org/1999/xlink';
+			const gradientNodes = xml.querySelectorAll( 'linearGradient, radialGradient' );
+			const ATTRS = [ 'x1', 'y1', 'x2', 'y2', 'cx', 'cy', 'r', 'fx', 'fy', 'gradientUnits', 'gradientTransform', 'spreadMethod' ];
+
+			const parsed = {};
+
+			for ( const node of gradientNodes ) {
+
+				const id = node.getAttribute( 'id' );
+				if ( ! id ) continue;
+
+				const entry = {
+					type: node.nodeName === 'radialGradient' ? 'radialGradient' : 'linearGradient',
+					attrs: {},
+					stops: null,
+					href: null,
+				};
+
+				const href = node.getAttributeNS( HREF_NS, 'href' ) || node.getAttribute( 'href' ) || '';
+				if ( href.startsWith( '#' ) ) entry.href = href.substring( 1 );
+
+				for ( const name of ATTRS ) {
+
+					if ( node.hasAttribute( name ) ) entry.attrs[ name ] = node.getAttribute( name );
+
+				}
+
+				const stopNodes = node.querySelectorAll( 'stop' );
+				if ( stopNodes.length > 0 ) {
+
+					entry.stops = [];
+					for ( const s of stopNodes ) {
+
+						let color = s.getAttribute( 'stop-color' );
+						if ( ! color && s.style ) color = s.style[ 'stop-color' ];
+						if ( ! color ) color = '#000';
+
+						let opacity = s.getAttribute( 'stop-opacity' );
+						if ( ( opacity === null || opacity === '' ) && s.style ) opacity = s.style[ 'stop-opacity' ];
+						opacity = ( opacity === null || opacity === '' || opacity === undefined )
+							? 1
+							: Math.max( 0, Math.min( 1, parseFloat( opacity ) ) );
+
+						const offset = Math.max( 0, Math.min( 1, parseFloat( s.getAttribute( 'offset' ) || '0' ) ) );
+						entry.stops.push( { offset, color, opacity } );
+
+					}
+
+				}
+
+				parsed[ id ] = entry;
+
+			}
+
+			function inherit( id, visited ) {
+
+				const entry = parsed[ id ];
+				if ( ! entry || visited.has( id ) ) return entry;
+				visited.add( id );
+
+				if ( entry.href && parsed[ entry.href ] ) {
+
+					const parent = inherit( entry.href, visited );
+					if ( parent ) {
+
+						if ( ! entry.stops ) entry.stops = parent.stops;
+						for ( const key in parent.attrs ) {
+
+							if ( ! ( key in entry.attrs ) ) entry.attrs[ key ] = parent.attrs[ key ];
+
+						}
+
+					}
+
+				}
+
+				return entry;
+
+			}
+
+			for ( const id in parsed ) inherit( id, new Set() );
+
+			for ( const id in parsed ) {
+
+				const entry = parsed[ id ];
+				const a = entry.attrs;
+				const units = a.gradientUnits === 'userSpaceOnUse' ? 'userSpaceOnUse' : 'objectBoundingBox';
+
+				const gradient = {
+					type: entry.type,
+					gradientUnits: units,
+					spreadMethod: a.spreadMethod === 'reflect' || a.spreadMethod === 'repeat' ? a.spreadMethod : 'pad',
+					gradientTransform: null,
+					stops: ( entry.stops || [] ).slice().sort( ( x, y ) => x.offset - y.offset ),
+				};
+
+				if ( a.gradientTransform ) {
+
+					gradient.gradientTransform = new Matrix3();
+					parseTransformString( a.gradientTransform, gradient.gradientTransform );
+
+				}
+
+				function coord( str ) {
+
+					if ( typeof str !== 'string' ) return 0;
+					if ( str.endsWith( '%' ) ) return parseFloat( str ) / 100;
+					return parseFloatWithUnits( str );
+
+				}
+
+				if ( entry.type === 'linearGradient' ) {
+
+					gradient.x1 = a.x1 !== undefined ? coord( a.x1 ) : 0;
+					gradient.y1 = a.y1 !== undefined ? coord( a.y1 ) : 0;
+					gradient.x2 = a.x2 !== undefined ? coord( a.x2 ) : ( units === 'objectBoundingBox' ? 1 : 0 );
+					gradient.y2 = a.y2 !== undefined ? coord( a.y2 ) : 0;
+
+				} else {
+
+					const defCenter = units === 'objectBoundingBox' ? 0.5 : 0;
+					const defR = units === 'objectBoundingBox' ? 0.5 : 0;
+					gradient.cx = a.cx !== undefined ? coord( a.cx ) : defCenter;
+					gradient.cy = a.cy !== undefined ? coord( a.cy ) : defCenter;
+					gradient.r = a.r !== undefined ? coord( a.r ) : defR;
+					gradient.fx = a.fx !== undefined ? coord( a.fx ) : gradient.cx;
+					gradient.fy = a.fy !== undefined ? coord( a.fy ) : gradient.cy;
+
+				}
+
+				gradients[ id ] = gradient;
+
+			}
+
+		}
+
+		//
+
 		function parseStyle( node, style ) {
 		function parseStyle( node, style ) {
 
 
 			style = Object.assign( {}, style ); // clone style
 			style = Object.assign( {}, style ); // clone style
@@ -1081,8 +1231,6 @@ class SVGLoader extends Loader {
 
 
 				if ( adjustFunction === undefined ) adjustFunction = function copy( v ) {
 				if ( adjustFunction === undefined ) adjustFunction = function copy( v ) {
 
 
-					if ( v.startsWith( 'url' ) ) console.warn( 'SVGLoader: url access in attributes is not implemented.' );
-
 					return v;
 					return v;
 
 
 				};
 				};
@@ -1504,7 +1652,6 @@ class SVGLoader extends Loader {
 		function parseNodeTransform( node ) {
 		function parseNodeTransform( node ) {
 
 
 			const transform = new Matrix3();
 			const transform = new Matrix3();
-			const currentTransform = tempTransform0;
 
 
 			if ( node.nodeName === 'use' && ( node.hasAttribute( 'x' ) || node.hasAttribute( 'y' ) ) ) {
 			if ( node.nodeName === 'use' && ( node.hasAttribute( 'x' ) || node.hasAttribute( 'y' ) ) ) {
 
 
@@ -1517,138 +1664,148 @@ class SVGLoader extends Loader {
 
 
 			if ( node.hasAttribute( 'transform' ) ) {
 			if ( node.hasAttribute( 'transform' ) ) {
 
 
-				const transformsTexts = node.getAttribute( 'transform' ).split( ')' );
+				parseTransformString( node.getAttribute( 'transform' ), transform );
 
 
-				for ( let tIndex = transformsTexts.length - 1; tIndex >= 0; tIndex -- ) {
+			}
 
 
-					const transformText = transformsTexts[ tIndex ].trim();
+			return transform;
 
 
-					if ( transformText === '' ) continue;
+		}
 
 
-					const openParPos = transformText.indexOf( '(' );
-					const closeParPos = transformText.length;
+		function parseTransformString( text, transform ) {
 
 
-					if ( openParPos > 0 && openParPos < closeParPos ) {
+			const currentTransform = tempTransform0;
 
 
-						const transformType = transformText.slice( 0, openParPos );
+			const transformsTexts = text.split( ')' );
 
 
-						const array = parseFloats( transformText.slice( openParPos + 1 ) );
+			for ( let tIndex = transformsTexts.length - 1; tIndex >= 0; tIndex -- ) {
 
 
-						currentTransform.identity();
+				const transformText = transformsTexts[ tIndex ].trim();
 
 
-						switch ( transformType ) {
+				if ( transformText === '' ) continue;
 
 
-							case 'translate':
+				const openParPos = transformText.indexOf( '(' );
+				const closeParPos = transformText.length;
 
 
-								if ( array.length >= 1 ) {
+				if ( openParPos > 0 && openParPos < closeParPos ) {
 
 
-									const tx = array[ 0 ];
-									let ty = 0;
+					const transformType = transformText.slice( 0, openParPos );
 
 
-									if ( array.length >= 2 ) {
+					const array = parseFloats( transformText.slice( openParPos + 1 ) );
 
 
-										ty = array[ 1 ];
+					currentTransform.identity();
 
 
-									}
+					switch ( transformType ) {
 
 
-									currentTransform.translate( tx, ty );
+						case 'translate':
+
+							if ( array.length >= 1 ) {
+
+								const tx = array[ 0 ];
+								let ty = 0;
+
+								if ( array.length >= 2 ) {
+
+									ty = array[ 1 ];
 
 
 								}
 								}
 
 
-								break;
+								currentTransform.translate( tx, ty );
 
 
-							case 'rotate':
+							}
 
 
-								if ( array.length >= 1 ) {
+							break;
 
 
-									let angle = 0;
-									let cx = 0;
-									let cy = 0;
+						case 'rotate':
 
 
-									// Angle
-									angle = array[ 0 ] * Math.PI / 180;
+							if ( array.length >= 1 ) {
 
 
-									if ( array.length >= 3 ) {
+								let angle = 0;
+								let cx = 0;
+								let cy = 0;
 
 
-										// Center x, y
-										cx = array[ 1 ];
-										cy = array[ 2 ];
+								// Angle
+								angle = array[ 0 ] * Math.PI / 180;
 
 
-									}
+								if ( array.length >= 3 ) {
 
 
-									// Rotate around center (cx, cy)
-									tempTransform1.makeTranslation( - cx, - cy );
-									tempTransform2.makeRotation( angle );
-									tempTransform3.multiplyMatrices( tempTransform2, tempTransform1 );
-									tempTransform1.makeTranslation( cx, cy );
-									currentTransform.multiplyMatrices( tempTransform1, tempTransform3 );
+									// Center x, y
+									cx = array[ 1 ];
+									cy = array[ 2 ];
 
 
 								}
 								}
 
 
-								break;
+								// Rotate around center (cx, cy)
+								tempTransform1.makeTranslation( - cx, - cy );
+								tempTransform2.makeRotation( angle );
+								tempTransform3.multiplyMatrices( tempTransform2, tempTransform1 );
+								tempTransform1.makeTranslation( cx, cy );
+								currentTransform.multiplyMatrices( tempTransform1, tempTransform3 );
 
 
-							case 'scale':
+							}
 
 
-								if ( array.length >= 1 ) {
+							break;
 
 
-									const scaleX = array[ 0 ];
-									let scaleY = scaleX;
+						case 'scale':
 
 
-									if ( array.length >= 2 ) {
+							if ( array.length >= 1 ) {
 
 
-										scaleY = array[ 1 ];
+								const scaleX = array[ 0 ];
+								let scaleY = scaleX;
 
 
-									}
+								if ( array.length >= 2 ) {
 
 
-									currentTransform.scale( scaleX, scaleY );
+									scaleY = array[ 1 ];
 
 
 								}
 								}
 
 
-								break;
+								currentTransform.scale( scaleX, scaleY );
 
 
-							case 'skewX':
+							}
 
 
-								if ( array.length === 1 ) {
+							break;
 
 
-									currentTransform.set(
-										1, Math.tan( array[ 0 ] * Math.PI / 180 ), 0,
-										0, 1, 0,
-										0, 0, 1
-									);
+						case 'skewX':
 
 
-								}
+							if ( array.length === 1 ) {
 
 
-								break;
+								currentTransform.set(
+									1, Math.tan( array[ 0 ] * Math.PI / 180 ), 0,
+									0, 1, 0,
+									0, 0, 1
+								);
 
 
-							case 'skewY':
+							}
 
 
-								if ( array.length === 1 ) {
+							break;
 
 
-									currentTransform.set(
-										1, 0, 0,
-										Math.tan( array[ 0 ] * Math.PI / 180 ), 1, 0,
-										0, 0, 1
-									);
+						case 'skewY':
 
 
-								}
+							if ( array.length === 1 ) {
 
 
-								break;
+								currentTransform.set(
+									1, 0, 0,
+									Math.tan( array[ 0 ] * Math.PI / 180 ), 1, 0,
+									0, 0, 1
+								);
 
 
-							case 'matrix':
+							}
 
 
-								if ( array.length === 6 ) {
+							break;
 
 
-									currentTransform.set(
-										array[ 0 ], array[ 2 ], array[ 4 ],
-										array[ 1 ], array[ 3 ], array[ 5 ],
-										0, 0, 1
-									);
+						case 'matrix':
 
 
-								}
+							if ( array.length === 6 ) {
 
 
-								break;
+								currentTransform.set(
+									array[ 0 ], array[ 2 ], array[ 4 ],
+									array[ 1 ], array[ 3 ], array[ 5 ],
+									0, 0, 1
+								);
 
 
-						}
+							}
+
+							break;
 
 
 					}
 					}
 
 
@@ -1972,6 +2129,7 @@ class SVGLoader extends Loader {
 
 
 		const paths = [];
 		const paths = [];
 		const stylesheets = {};
 		const stylesheets = {};
+		const gradients = {};
 
 
 		const transformStack = [];
 		const transformStack = [];
 
 
@@ -1986,6 +2144,8 @@ class SVGLoader extends Loader {
 
 
 		const xml = new DOMParser().parseFromString( text, 'image/svg+xml' ); // application/xml
 		const xml = new DOMParser().parseFromString( text, 'image/svg+xml' ); // application/xml
 
 
+		parseGradients( xml );
+
 		parseNode( xml.documentElement, {
 		parseNode( xml.documentElement, {
 			fill: '#000',
 			fill: '#000',
 			fillOpacity: 1,
 			fillOpacity: 1,
@@ -1996,7 +2156,7 @@ class SVGLoader extends Loader {
 			strokeMiterLimit: 4
 			strokeMiterLimit: 4
 		} );
 		} );
 
 
-		const data = { paths: paths, xml: xml.documentElement };
+		const data = { paths: paths, gradients: gradients, xml: xml.documentElement };
 
 
 		// console.log( paths );
 		// console.log( paths );
 		return data;
 		return data;
@@ -2014,14 +2174,37 @@ class SVGLoader extends Loader {
 		const style = shapePath.userData.style;
 		const style = shapePath.userData.style;
 		if ( style.fill === undefined || style.fill === 'none' ) return null;
 		if ( style.fill === undefined || style.fill === 'none' ) return null;
 
 
-		return new MeshBasicMaterial( {
-			color: shapePath.color,
+		const color = shapePath.color;
+		let texture = null;
+
+		const urlMatch = GRADIENT_URL_RE.exec( style.fill );
+
+		if ( urlMatch ) {
+
+			const gradient = shapePath.userData.gradients && shapePath.userData.gradients[ urlMatch[ 1 ] ];
+			texture = buildGradientTexture( gradient, shapePath );
+
+		}
+
+		const material = new MeshBasicMaterial( {
 			opacity: style.fillOpacity * ( style.opacity || 1 ),
 			opacity: style.fillOpacity * ( style.opacity || 1 ),
 			transparent: true,
 			transparent: true,
 			side: DoubleSide,
 			side: DoubleSide,
 			depthWrite: false,
 			depthWrite: false,
 		} );
 		} );
 
 
+		if ( texture !== null ) {
+
+			material.map = texture;
+
+		} else {
+
+			material.color = color;
+
+		}
+
+		return material;
+
 	}
 	}
 
 
 	/**
 	/**
@@ -2033,8 +2216,15 @@ class SVGLoader extends Loader {
 	static createStrokeMaterial( shapePath ) {
 	static createStrokeMaterial( shapePath ) {
 
 
 		const style = shapePath.userData.style;
 		const style = shapePath.userData.style;
+
 		if ( style.stroke === undefined || style.stroke === 'none' ) return null;
 		if ( style.stroke === undefined || style.stroke === 'none' ) return null;
 
 
+		if ( GRADIENT_URL_RE.test( style.stroke ) ) {
+
+			console.warn( 'THREE.SVGLoader: Gradient strokes are not supported.' );
+
+		}
+
 		return new MeshBasicMaterial( {
 		return new MeshBasicMaterial( {
 			color: new Color().setStyle( style.stroke, COLOR_SPACE_SVG ),
 			color: new Color().setStyle( style.stroke, COLOR_SPACE_SVG ),
 			opacity: style.strokeOpacity * ( style.opacity || 1 ),
 			opacity: style.strokeOpacity * ( style.opacity || 1 ),
@@ -3081,6 +3271,203 @@ class SVGLoader extends Loader {
 
 
 	}
 	}
 
 
+}
+
+const GRADIENT_URL_RE = /^\s*url\(\s*(?:["']\s*)?#([^)'"\s]+)(?:\s*["'])?\s*\)\s*$/;
+
+// Bakes a gradient into a CanvasTexture in its own local frame and configures
+// `texture.matrix` (with `matrixAutoUpdate = false`) so that shape-space UVs —
+// which, because transformPath bakes the world matrix into geometry vertex
+// positions, equal world xy — sample the correct gradient color. The caller
+// just sets `material.map = texture`; no bounding box, no geometry, no
+// per-vertex UV work required.
+function buildGradientTexture( gradient, shapePath, resolution = 256 ) {
+
+	if ( ! gradient || ! Array.isArray( gradient.stops ) || gradient.stops.length === 0 ) return null;
+
+	const worldTransform = shapePath.userData.transform;
+	const isBBoxUnits = gradient.gradientUnits === 'objectBoundingBox';
+
+	// For objectBoundingBox gradients we need the element's local bounding
+	// box. Path points are in world space (transformPath already applied the
+	// world transform), so invert that first.
+	let localBBox = null;
+
+	if ( isBBoxUnits ) {
+
+		localBBox = computeLocalBBox( shapePath, worldTransform );
+		if ( localBBox === null ) return null;
+
+	}
+
+	// Resolves a gradient-space point to the geometry's (world) coordinate
+	// space: gradient coord → gradientTransform → target coord → (for
+	// objectBoundingBox: bbox → local) → worldTransform → world.
+	function resolvePoint( x, y, out ) {
+
+		out.set( x, y, 1 );
+		if ( gradient.gradientTransform ) out.applyMatrix3( gradient.gradientTransform );
+		if ( isBBoxUnits ) out.set(
+			localBBox.minX + out.x * localBBox.width,
+			localBBox.minY + out.y * localBBox.height,
+			1,
+		);
+		if ( worldTransform ) out.applyMatrix3( worldTransform );
+
+	}
+
+	const canvas = document.createElement( 'canvas' );
+	let textureMatrix;
+
+	if ( gradient.type === 'linearGradient' ) {
+
+		// 1D bake along the gradient vector.
+		canvas.width = resolution;
+		canvas.height = 1;
+		const ctx = canvas.getContext( '2d' );
+
+		const grad = ctx.createLinearGradient( 0, 0, resolution, 0 );
+		addStops( grad, gradient.stops );
+		ctx.fillStyle = grad;
+		ctx.fillRect( 0, 0, resolution, 1 );
+
+		const p1 = new Vector3();
+		const p2 = new Vector3();
+		resolvePoint( gradient.x1, gradient.y1, p1 );
+		resolvePoint( gradient.x2, gradient.y2, p2 );
+
+		const dx = p2.x - p1.x;
+		const dy = p2.y - p1.y;
+		const len2 = dx * dx + dy * dy || 1e-20;
+		const a = dx / len2;
+		const b = dy / len2;
+		const c = - ( a * p1.x + b * p1.y );
+
+		// M * (vx, vy, 1) = (t, 0.5, 1)
+		textureMatrix = new Matrix3().set(
+			a, b, c,
+			0, 0, 0.5,
+			0, 0, 1,
+		);
+
+	} else {
+
+		// Resolve cx/cy/fx/fy into local space and scale r per the SVG spec
+		// (objectBoundingBox scales lengths by sqrt((w² + h²) / 2)). The canvas
+		// only draws circular radial gradients, so any ellipticity induced by
+		// a non-uniform world transform is picked up later via the UV matrix.
+		let cx = gradient.cx, cy = gradient.cy;
+		let fx = gradient.fx, fy = gradient.fy;
+		let r = gradient.r;
+
+		if ( gradient.gradientTransform ) {
+
+			const tmp = new Vector3();
+			tmp.set( cx, cy, 1 ).applyMatrix3( gradient.gradientTransform );
+			cx = tmp.x; cy = tmp.y;
+			tmp.set( fx, fy, 1 ).applyMatrix3( gradient.gradientTransform );
+			fx = tmp.x; fy = tmp.y;
+
+		}
+
+		if ( isBBoxUnits ) {
+
+			cx = localBBox.minX + cx * localBBox.width;
+			cy = localBBox.minY + cy * localBBox.height;
+			fx = localBBox.minX + fx * localBBox.width;
+			fy = localBBox.minY + fy * localBBox.height;
+			r = r * Math.sqrt( ( localBBox.width * localBBox.width + localBBox.height * localBBox.height ) / 2 );
+
+		}
+
+		if ( r <= 0 ) return null;
+
+		// 2D bake in the gradient's local frame, covering [cx-r, cx+r]².
+		canvas.width = resolution;
+		canvas.height = resolution;
+		const ctx = canvas.getContext( '2d' );
+
+		const localMinX = cx - r;
+		const localMinY = cy - r;
+		const localSpan = 2 * r;
+		const scale = resolution / localSpan;
+
+		// Canvas pixel = (local - localMin) * scale.
+		ctx.setTransform( scale, 0, 0, scale, - localMinX * scale, - localMinY * scale );
+
+		const grad = ctx.createRadialGradient( fx, fy, 0, cx, cy, r );
+		addStops( grad, gradient.stops );
+		ctx.fillStyle = grad;
+		ctx.fillRect( localMinX, localMinY, localSpan, localSpan );
+
+		// UV matrix: world → local (via worldTransform⁻¹) → normalized canvas UV.
+		const inv = worldTransform ? worldTransform.clone().invert() : new Matrix3();
+		const norm = new Matrix3().set(
+			1 / localSpan, 0, - localMinX / localSpan,
+			0, 1 / localSpan, - localMinY / localSpan,
+			0, 0, 1,
+		);
+		textureMatrix = norm.multiply( inv );
+
+	}
+
+	const texture = new CanvasTexture( canvas );
+	texture.colorSpace = COLOR_SPACE_SVG;
+	texture.flipY = false;
+	texture.matrixAutoUpdate = false;
+	texture.matrix = textureMatrix;
+
+	const wrap = gradient.spreadMethod === 'reflect' ? MirroredRepeatWrapping
+		: gradient.spreadMethod === 'repeat' ? RepeatWrapping
+			: ClampToEdgeWrapping;
+	texture.wrapS = wrap;
+	texture.wrapT = wrap;
+
+	return texture;
+
+}
+
+function computeLocalBBox( shapePath, worldTransform ) {
+
+	const inv = worldTransform ? worldTransform.clone().invert() : null;
+	const tmp = new Vector2();
+	const box = new Box2();
+
+	for ( const subPath of shapePath.subPaths ) {
+
+		for ( const p of subPath.getPoints() ) {
+
+			tmp.copy( p );
+			if ( inv ) tmp.applyMatrix3( inv );
+			box.expandByPoint( tmp );
+
+		}
+
+	}
+
+	if ( box.isEmpty() ) return null;
+
+	return { minX: box.min.x, minY: box.min.y, width: box.max.x - box.min.x, height: box.max.y - box.min.y };
+
+}
+
+function addStops( canvasGradient, stops ) {
+
+	const tmpColor = new Color();
+	for ( const stop of stops ) {
+
+		let css = stop.color;
+		if ( stop.opacity < 1 ) {
+
+			tmpColor.setStyle( stop.color, COLOR_SPACE_SVG );
+			const m = /rgb\(([^)]+)\)/.exec( tmpColor.getStyle( COLOR_SPACE_SVG ) );
+			if ( m ) css = `rgba(${m[ 1 ]},${stop.opacity})`;
+
+		}
+
+		canvasGradient.addColorStop( Math.max( 0, Math.min( 1, stop.offset ) ), css );
+
+	}
 
 
 }
 }
 
 

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 0 - 0
examples/models/svg/blueprint.svg


+ 3 - 1
examples/webgl_loader_svg.html

@@ -121,6 +121,7 @@
 					'singlePointTest3': 'models/svg/singlePointTest3.svg',
 					'singlePointTest3': 'models/svg/singlePointTest3.svg',
 					'emptyPath': 'models/svg/emptyPath.svg',
 					'emptyPath': 'models/svg/emptyPath.svg',
 					'emoji': 'models/svg/emoji.svg',
 					'emoji': 'models/svg/emoji.svg',
+					'blueprint': 'models/svg/blueprint.svg',
 
 
 				} ).name( 'SVG File' ).onChange( update );
 				} ).name( 'SVG File' ).onChange( update );
 
 
@@ -253,10 +254,11 @@
 			function disposeScene( scene ) {
 			function disposeScene( scene ) {
 
 
 				scene.traverse( function ( object ) {
 				scene.traverse( function ( object ) {
-			
+
 					if ( object.isMesh || object.isLine ) {
 					if ( object.isMesh || object.isLine ) {
 
 
 						object.geometry.dispose();
 						object.geometry.dispose();
+						if ( object.material.map ) object.material.map.dispose();
 						object.material.dispose();
 						object.material.dispose();
 
 
 					}
 					}

Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio

粤ICP备19079148号