diff --git a/examples/jsm/loaders/SVGLoader.js b/examples/jsm/loaders/SVGLoader.js
index 12f914b07433a4..98e2c33dd3756f 100644
--- a/examples/jsm/loaders/SVGLoader.js
+++ b/examples/jsm/loaders/SVGLoader.js
@@ -1,6 +1,8 @@
import {
Box2,
BufferGeometry,
+ CanvasTexture,
+ ClampToEdgeWrapping,
Color,
DoubleSide,
FileLoader,
@@ -8,7 +10,9 @@ import {
Loader,
Matrix3,
MeshBasicMaterial,
+ MirroredRepeatWrapping,
Path,
+ RepeatWrapping,
Shape,
ShapePath,
ShapeUtils,
@@ -147,6 +151,12 @@ class SVGLoader extends Loader {
if ( node.nodeType !== 1 ) return;
+ if ( node.hasAttribute( 'filter' ) ) {
+
+ console.warn( 'THREE.SVGLoader: Filters are not supported.' );
+
+ }
+
const transform = getNodeTransform( node );
let isDefsNode = false;
@@ -231,7 +241,7 @@ class SVGLoader extends Loader {
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 );
@@ -244,7 +254,7 @@ class SVGLoader extends Loader {
const pathStyle = Object.assign( {}, style );
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 ) {
style = Object.assign( {}, style ); // clone style
@@ -1081,8 +1231,6 @@ class SVGLoader extends Loader {
if ( adjustFunction === undefined ) adjustFunction = function copy( v ) {
- if ( v.startsWith( 'url' ) ) console.warn( 'SVGLoader: url access in attributes is not implemented.' );
-
return v;
};
@@ -1504,7 +1652,6 @@ class SVGLoader extends Loader {
function parseNodeTransform( node ) {
const transform = new Matrix3();
- const currentTransform = tempTransform0;
if ( node.nodeName === 'use' && ( node.hasAttribute( 'x' ) || node.hasAttribute( 'y' ) ) ) {
@@ -1517,138 +1664,148 @@ class SVGLoader extends Loader {
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 stylesheets = {};
+ const gradients = {};
const transformStack = [];
@@ -1986,6 +2144,8 @@ class SVGLoader extends Loader {
const xml = new DOMParser().parseFromString( text, 'image/svg+xml' ); // application/xml
+ parseGradients( xml );
+
parseNode( xml.documentElement, {
fill: '#000',
fillOpacity: 1,
@@ -1996,7 +2156,7 @@ class SVGLoader extends Loader {
strokeMiterLimit: 4
} );
- const data = { paths: paths, xml: xml.documentElement };
+ const data = { paths: paths, gradients: gradients, xml: xml.documentElement };
// console.log( paths );
return data;
@@ -2014,14 +2174,37 @@ class SVGLoader extends Loader {
const style = shapePath.userData.style;
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 ),
transparent: true,
side: DoubleSide,
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 ) {
const style = shapePath.userData.style;
+
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( {
color: new Color().setStyle( style.stroke, COLOR_SPACE_SVG ),
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 );
+
+ }
}
diff --git a/examples/models/svg/blueprint.svg b/examples/models/svg/blueprint.svg
new file mode 100644
index 00000000000000..35b9a424bf3698
--- /dev/null
+++ b/examples/models/svg/blueprint.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/examples/webgl_loader_svg.html b/examples/webgl_loader_svg.html
index f54681485bb79a..aec169ee119911 100644
--- a/examples/webgl_loader_svg.html
+++ b/examples/webgl_loader_svg.html
@@ -121,6 +121,7 @@
'singlePointTest3': 'models/svg/singlePointTest3.svg',
'emptyPath': 'models/svg/emptyPath.svg',
'emoji': 'models/svg/emoji.svg',
+ 'blueprint': 'models/svg/blueprint.svg',
} ).name( 'SVG File' ).onChange( update );
@@ -253,10 +254,11 @@
function disposeScene( scene ) {
scene.traverse( function ( object ) {
-
+
if ( object.isMesh || object.isLine ) {
object.geometry.dispose();
+ if ( object.material.map ) object.material.map.dispose();
object.material.dispose();
}
diff --git a/src/nodes/accessors/BufferAttributeNode.js b/src/nodes/accessors/BufferAttributeNode.js
index 874439fa019a67..b737aa886e36c5 100644
--- a/src/nodes/accessors/BufferAttributeNode.js
+++ b/src/nodes/accessors/BufferAttributeNode.js
@@ -265,8 +265,11 @@ class BufferAttributeNode extends InputNode {
generate( builder ) {
const nodeType = this.getNodeType( builder );
+ const nodeName = builder.context.nodeName;
- const nodeAttribute = builder.getBufferAttributeFromNode( this, nodeType );
+ if ( nodeName !== undefined ) delete builder.context.nodeName; // deleting when consumed
+
+ const nodeAttribute = builder.getBufferAttributeFromNode( this, nodeType, nodeName );
const propertyName = builder.getPropertyName( nodeAttribute );
let output = null;
@@ -279,7 +282,15 @@ class BufferAttributeNode extends InputNode {
} else {
- const nodeVarying = varying( this );
+ let varyingName;
+
+ if ( nodeName ) {
+
+ varyingName = nodeName + 'Varying';
+
+ }
+
+ const nodeVarying = varying( this, varyingName );
output = nodeVarying.build( builder, nodeType );
diff --git a/src/nodes/core/NodeBuilder.js b/src/nodes/core/NodeBuilder.js
index ff93d5440ad6f0..908c0e8685ceda 100644
--- a/src/nodes/core/NodeBuilder.js
+++ b/src/nodes/core/NodeBuilder.js
@@ -1839,9 +1839,10 @@ class NodeBuilder {
*
* @param {BufferAttributeNode} node - The buffer attribute node.
* @param {string} type - The node type.
+ * @param {?string} [name=null] - The name of the buffer attribute.
* @return {NodeAttribute} The node attribute.
*/
- getBufferAttributeFromNode( node, type ) {
+ getBufferAttributeFromNode( node, type, name = null ) {
const nodeData = this.getDataFromNode( node, 'vertex' );
@@ -1851,7 +1852,13 @@ class NodeBuilder {
const index = this.uniforms.index ++;
- bufferAttribute = new NodeAttribute( 'nodeAttribute' + index, type, node );
+ if ( name === null ) {
+
+ name = 'nodeAttribute' + index;
+
+ }
+
+ bufferAttribute = new NodeAttribute( name, type, node );
this.bufferAttributes.push( bufferAttribute );
diff --git a/src/renderers/common/UniformsGroup.js b/src/renderers/common/UniformsGroup.js
index 0f29befde80f5e..154338ced99c73 100644
--- a/src/renderers/common/UniformsGroup.js
+++ b/src/renderers/common/UniformsGroup.js
@@ -55,6 +55,15 @@ class UniformsGroup extends UniformBuffer {
*/
this._updateRangeCache = new Map();
+ /**
+ * Uniform indices whose range has already been pushed into `updateRanges`
+ * during the current update cycle. Reset on `clearUpdateRanges()`.
+ *
+ * @private
+ * @type {Set}
+ */
+ this._addedIndices = new Set();
+
}
/**
@@ -66,21 +75,23 @@ class UniformsGroup extends UniformBuffer {
const index = uniform.index;
- if ( this._updateRangeCache.has( index ) !== true ) {
+ if ( this._addedIndices.has( index ) ) return;
- const updateRanges = this.updateRanges;
+ let range = this._updateRangeCache.get( index );
- const start = uniform.offset;
- const count = uniform.itemSize;
-
- const range = { start, count };
-
- updateRanges.push( range );
+ if ( range === undefined ) {
+ range = { start: 0, count: 0 };
this._updateRangeCache.set( index, range );
}
+ range.start = uniform.offset;
+ range.count = uniform.itemSize;
+
+ this._addedIndices.add( index );
+ this.updateRanges.push( range );
+
}
/**
@@ -88,7 +99,7 @@ class UniformsGroup extends UniformBuffer {
*/
clearUpdateRanges() {
- this._updateRangeCache.clear();
+ this._addedIndices.clear();
super.clearUpdateRanges();
diff --git a/src/renderers/shaders/ShaderChunk/common.glsl.js b/src/renderers/shaders/ShaderChunk/common.glsl.js
index 771d504a8ef5e3..63e925c7e75295 100644
--- a/src/renderers/shaders/ShaderChunk/common.glsl.js
+++ b/src/renderers/shaders/ShaderChunk/common.glsl.js
@@ -64,7 +64,7 @@ vec3 transformDirection( in vec3 dir, in mat4 matrix ) {
}
-#define inverseTransformDirection transformNormalByInverseViewMatrix // keeping this for backwards compatibility
+#define inverseTransformDirection transformDirectionByInverseViewMatrix // keeping this for backwards compatibility
vec3 transformNormalByInverseViewMatrix( in vec3 normal, in mat4 viewMatrix ) {