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 ) {