Skip to content

⬅️ Back to Table of Contents

📄 NodeMaterialObserver.js

📊 Analysis Summary

Metric Count
🔧 Functions 10
🧱 Classes 1
📊 Variables & Constants 23

📚 Table of Contents

🛠️ File Location:

📂 src/materials/nodes/manager/NodeMaterialObserver.js

Variables & Constants

Name Type Kind Value Exported
refreshUniforms string[] let/var [ 'alphaMap', 'alphaTest', 'anisotropy', 'anisotropyMap', 'anisotropyRotation...
_lightsCache WeakMap<LightsNode, any> let/var new WeakMap()
attributesData {} let/var {}
attribute any let/var attributes[ name ]
material any let/var builder.material
data {} let/var {}
value Material let/var material[ property ]
materialData any let/var renderObjectData.material
value any let/var materialData[ property ]
mtlValue RenderObject let/var material[ property ]
storedGeometryData any let/var renderObjectData.geometry
attributes any let/var geometry.attributes
storedAttributes any let/var storedGeometryData.attributes
storedAttributeData any let/var storedAttributes[ name ]
attribute any let/var attributes[ name ]
index any let/var geometry.index
storedIndexVersion any let/var storedGeometryData.indexVersion
currentIndexVersion any let/var index ? index.version : null
morphChanged boolean let/var false
lights any[] let/var []
isStatic boolean let/var renderObject.object.static === true
isBundle boolean let/var renderObject.bundle !== null && renderObject.bundle.static === true && this.g...
notEqual boolean let/var this.equals( renderObject, lightsData ) !== true

Functions

NodeMaterialObserver.firstInitialization(renderObject: RenderObject): boolean

JSDoc:

/**
     * Returns `true` if the given render object is verified for the first time of this observer.
     *
     * @param {RenderObject} renderObject - The render object.
     * @return {boolean} Whether the given render object is verified for the first time of this observer.
     */

Parameters:

  • renderObject RenderObject

Returns: boolean

Calls:

  • this.renderObjects.has
  • this.getRenderObjectData
Code
firstInitialization( renderObject ) {

        const hasInitialized = this.renderObjects.has( renderObject );

        if ( hasInitialized === false ) {

            this.getRenderObjectData( renderObject );

            return true;

        }

        return false;

    }

NodeMaterialObserver.needsVelocity(renderer: Renderer): boolean

JSDoc:

/**
     * Returns `true` if the current rendering produces motion vectors.
     *
     * @param {Renderer} renderer - The renderer.
     * @return {boolean} Whether the current rendering produces motion vectors or not.
     */

Parameters:

  • renderer Renderer

Returns: boolean

Calls:

  • renderer.getMRT
  • mrt.has
Code
needsVelocity( renderer ) {

        const mrt = renderer.getMRT();

        return ( mrt !== null && mrt.has( 'velocity' ) );

    }

NodeMaterialObserver.getRenderObjectData(renderObject: RenderObject): any

JSDoc:

/**
     * Returns monitoring data for the given render object.
     *
     * @param {RenderObject} renderObject - The render object.
     * @return {Object} The monitoring data.
     */

Parameters:

  • renderObject RenderObject

Returns: any

Calls:

  • this.renderObjects.get
  • this.getMaterialData
  • this.getAttributesData
  • object.matrixWorld.clone
  • object.center.clone
  • object.morphTargetInfluences.slice
  • this.getLightsData
  • renderObject.lightsNode.getLights
  • this.renderObjects.set
Code
getRenderObjectData( renderObject ) {

        let data = this.renderObjects.get( renderObject );

        if ( data === undefined ) {

            const { geometry, material, object } = renderObject;

            data = {
                material: this.getMaterialData( material ),
                geometry: {
                    id: geometry.id,
                    attributes: this.getAttributesData( geometry.attributes ),
                    indexVersion: geometry.index ? geometry.index.version : null,
                    drawRange: { start: geometry.drawRange.start, count: geometry.drawRange.count }
                },
                worldMatrix: object.matrixWorld.clone()
            };

            if ( object.center ) {

                data.center = object.center.clone();

            }

            if ( object.morphTargetInfluences ) {

                data.morphTargetInfluences = object.morphTargetInfluences.slice();

            }

            if ( renderObject.bundle !== null ) {

                data.version = renderObject.bundle.version;

            }

            if ( data.material.transmission > 0 ) {

                const { width, height } = renderObject.context;

                data.bufferWidth = width;
                data.bufferHeight = height;

            }

            data.lights = this.getLightsData( renderObject.lightsNode.getLights() );

            this.renderObjects.set( renderObject, data );

        }

        return data;

    }

NodeMaterialObserver.getAttributesData(attributes: any): any

JSDoc:

/**
     * Returns an attribute data structure holding the attributes versions for
     * monitoring.
     *
     * @param {Object} attributes - The geometry attributes.
     * @return {Object} An object for monitoring the versions of attributes.
     */

Parameters:

  • attributes any

Returns: any

Code
getAttributesData( attributes ) {

        const attributesData = {};

        for ( const name in attributes ) {

            const attribute = attributes[ name ];

            attributesData[ name ] = {
                version: attribute.version
            };

        }

        return attributesData;

    }

NodeMaterialObserver.containsNode(builder: NodeBuilder): boolean

JSDoc:

/**
     * Returns `true` if the node builder's material uses
     * node properties.
     *
     * @param {NodeBuilder} builder - The current node builder.
     * @return {boolean} Whether the node builder's material uses node properties or not.
     */

Parameters:

  • builder NodeBuilder

Returns: boolean

Code
containsNode( builder ) {

        const material = builder.material;

        for ( const property in material ) {

            if ( material[ property ] && material[ property ].isNode )
                return true;

        }

        if ( builder.renderer.overrideNodes.modelViewMatrix !== null || builder.renderer.overrideNodes.modelNormalViewMatrix !== null )
            return true;

        return false;

    }

NodeMaterialObserver.getMaterialData(material: Material): any

JSDoc:

/**
     * Returns a material data structure holding the material property values for
     * monitoring.
     *
     * @param {Material} material - The material.
     * @return {Object} An object for monitoring material properties.
     */

Parameters:

  • material Material

Returns: any

Calls:

  • value.clone
Code
getMaterialData( material ) {

        const data = {};

        for ( const property of this.refreshUniforms ) {

            const value = material[ property ];

            if ( value === null || value === undefined ) continue;

            if ( typeof value === 'object' && value.clone !== undefined ) {

                if ( value.isTexture === true ) {

                    data[ property ] = { id: value.id, version: value.version };

                } else {

                    data[ property ] = value.clone();

                }

            } else {

                data[ property ] = value;

            }

        }

        return data;

    }

NodeMaterialObserver.equals(renderObject: RenderObject, lightsData: Light[]): boolean

JSDoc:

/**
     * Returns `true` if the given render object has not changed its state.
     *
     * @param {RenderObject} renderObject - The render object.
     * @param {Array<Light>} lightsData - The current material lights.
     * @return {boolean} Whether the given render object has changed its state or not.
     */

Parameters:

  • renderObject RenderObject
  • lightsData Light[]

Returns: boolean

Calls:

  • this.getRenderObjectData
  • renderObjectData.worldMatrix.equals
  • renderObjectData.worldMatrix.copy
  • value.equals
  • value.copy
  • Object.keys
  • this.getAttributesData
  • renderObjectData.center.equals
  • renderObjectData.center.copy

Internal Comments:

// world matrix
// material (x2)
// geometry (x2)
// compare each attribute
// attribute was removed (x2)
// check index (x2)
// check drawRange
// morph targets
// lights
// center
// bundle

Code
equals( renderObject, lightsData ) {

        const { object, material, geometry } = renderObject;

        const renderObjectData = this.getRenderObjectData( renderObject );

        // world matrix

        if ( renderObjectData.worldMatrix.equals( object.matrixWorld ) !== true ) {

            renderObjectData.worldMatrix.copy( object.matrixWorld );

            return false;

        }

        // material

        const materialData = renderObjectData.material;

        for ( const property in materialData ) {

            const value = materialData[ property ];
            const mtlValue = material[ property ];

            if ( value.equals !== undefined ) {

                if ( value.equals( mtlValue ) === false ) {

                    value.copy( mtlValue );

                    return false;

                }

            } else if ( mtlValue.isTexture === true ) {

                if ( value.id !== mtlValue.id || value.version !== mtlValue.version ) {

                    value.id = mtlValue.id;
                    value.version = mtlValue.version;

                    return false;

                }

            } else if ( value !== mtlValue ) {

                materialData[ property ] = mtlValue;

                return false;

            }

        }

        if ( materialData.transmission > 0 ) {

            const { width, height } = renderObject.context;

            if ( renderObjectData.bufferWidth !== width || renderObjectData.bufferHeight !== height ) {

                renderObjectData.bufferWidth = width;
                renderObjectData.bufferHeight = height;

                return false;

            }

        }

        // geometry

        const storedGeometryData = renderObjectData.geometry;
        const attributes = geometry.attributes;
        const storedAttributes = storedGeometryData.attributes;

        const storedAttributeNames = Object.keys( storedAttributes );
        const currentAttributeNames = Object.keys( attributes );

        if ( storedGeometryData.id !== geometry.id ) {

            storedGeometryData.id = geometry.id;
            return false;

        }

        if ( storedAttributeNames.length !== currentAttributeNames.length ) {

            renderObjectData.geometry.attributes = this.getAttributesData( attributes );
            return false;

        }

        // compare each attribute

        for ( const name of storedAttributeNames ) {

            const storedAttributeData = storedAttributes[ name ];
            const attribute = attributes[ name ];

            if ( attribute === undefined ) {

                // attribute was removed
                delete storedAttributes[ name ];
                return false;

            }

            if ( storedAttributeData.version !== attribute.version ) {

                storedAttributeData.version = attribute.version;
                return false;

            }

        }

        // check index

        const index = geometry.index;
        const storedIndexVersion = storedGeometryData.indexVersion;
        const currentIndexVersion = index ? index.version : null;

        if ( storedIndexVersion !== currentIndexVersion ) {

            storedGeometryData.indexVersion = currentIndexVersion;
            return false;

        }

        // check drawRange

        if ( storedGeometryData.drawRange.start !== geometry.drawRange.start || storedGeometryData.drawRange.count !== geometry.drawRange.count ) {

            storedGeometryData.drawRange.start = geometry.drawRange.start;
            storedGeometryData.drawRange.count = geometry.drawRange.count;
            return false;

        }

        // morph targets

        if ( renderObjectData.morphTargetInfluences ) {

            let morphChanged = false;

            for ( let i = 0; i < renderObjectData.morphTargetInfluences.length; i ++ ) {

                if ( renderObjectData.morphTargetInfluences[ i ] !== object.morphTargetInfluences[ i ] ) {

                    morphChanged = true;

                }

            }

            if ( morphChanged ) return true;

        }

        // lights

        if ( renderObjectData.lights ) {

            for ( let i = 0; i < lightsData.length; i ++ ) {

                if ( renderObjectData.lights[ i ].map !== lightsData[ i ].map ) {

                    return false;

                }

            }

        }

        // center

        if ( renderObjectData.center ) {

            if ( renderObjectData.center.equals( object.center ) === false ) {

                renderObjectData.center.copy( object.center );

                return true;

            }

        }

        // bundle

        if ( renderObject.bundle !== null ) {

            renderObjectData.version = renderObject.bundle.version;

        }

        return true;

    }

NodeMaterialObserver.getLightsData(materialLights: Light[]): any[]

JSDoc:

/**
     * Returns the lights data for the given material lights.
     *
     * @param {Array<Light>} materialLights - The material lights.
     * @return {Array<Object>} The lights data for the given material lights.
     */

Parameters:

  • materialLights Light[]

Returns: any[]

Calls:

  • lights.push

Internal Comments:

// only add lights that have a map (x4)

Code
getLightsData( materialLights ) {

        const lights = [];

        for ( const light of materialLights ) {

            if ( light.isSpotLight === true && light.map !== null ) {

                // only add lights that have a map

                lights.push( { map: light.map.version } );

            }

        }

        return lights;

    }

NodeMaterialObserver.getLights(lightsNode: LightsNode, renderId: number): any[]

JSDoc:

/**
     * Returns the lights for the given lights node and render ID.
     *
     * @param {LightsNode} lightsNode - The lights node.
     * @param {number} renderId - The render ID.
     * @return {Array} The lights for the given lights node and render ID.
     */

Parameters:

  • lightsNode LightsNode
  • renderId number

Returns: any[]

Calls:

  • _lightsCache.has
  • _lightsCache.get
  • this.getLightsData
  • lightsNode.getLights
  • _lightsCache.set
Code
getLights( lightsNode, renderId ) {

        if ( _lightsCache.has( lightsNode ) ) {

            const cached = _lightsCache.get( lightsNode );

            if ( cached.renderId === renderId ) {

                return cached.lightsData;

            }

        }

        const lightsData = this.getLightsData( lightsNode.getLights() );

        _lightsCache.set( lightsNode, { renderId, lightsData } );

        return lightsData;

    }

NodeMaterialObserver.needsRefresh(renderObject: RenderObject, nodeFrame: NodeFrame): boolean

JSDoc:

/**
     * Checks if the given render object requires a refresh.
     *
     * @param {RenderObject} renderObject - The render object.
     * @param {NodeFrame} nodeFrame - The current node frame.
     * @return {boolean} Whether the given render object requires a refresh or not.
     */

Parameters:

  • renderObject RenderObject
  • nodeFrame NodeFrame

Returns: boolean

Calls:

  • this.firstInitialization
  • this.needsVelocity
  • this.getRenderObjectData
  • this.getLights
  • this.equals
Code
needsRefresh( renderObject, nodeFrame ) {

        if ( this.hasNode || this.hasAnimation || this.firstInitialization( renderObject ) || this.needsVelocity( nodeFrame.renderer ) )
            return true;

        const { renderId } = nodeFrame;

        if ( this.renderId !== renderId ) {

            this.renderId = renderId;

            return true;

        }

        const isStatic = renderObject.object.static === true;
        const isBundle = renderObject.bundle !== null && renderObject.bundle.static === true && this.getRenderObjectData( renderObject ).version === renderObject.bundle.version;

        if ( isStatic || isBundle )
            return false;

        const lightsData = this.getLights( renderObject.lightsNode, renderId );
        const notEqual = this.equals( renderObject, lightsData ) !== true;

        return notEqual;

    }

Classes

NodeMaterialObserver

Class Code
class NodeMaterialObserver {

    /**
     * Constructs a new node material observer.
     *
     * @param {NodeBuilder} builder - The node builder.
     */
    constructor( builder ) {

        /**
         * A node material can be used by more than one render object so the
         * monitor must maintain a list of render objects.
         *
         * @type {WeakMap<RenderObject,Object>}
         */
        this.renderObjects = new WeakMap();

        /**
         * Whether the material uses node objects or not.
         *
         * @type {boolean}
         */
        this.hasNode = this.containsNode( builder );

        /**
         * Whether the node builder's 3D object is animated or not.
         *
         * @type {boolean}
         */
        this.hasAnimation = builder.object.isSkinnedMesh === true;

        /**
         * A list of all possible material uniforms
         *
         * @type {Array<string>}
         */
        this.refreshUniforms = refreshUniforms;

        /**
         * Holds the current render ID from the node frame.
         *
         * @type {number}
         * @default 0
         */
        this.renderId = 0;

    }

    /**
     * Returns `true` if the given render object is verified for the first time of this observer.
     *
     * @param {RenderObject} renderObject - The render object.
     * @return {boolean} Whether the given render object is verified for the first time of this observer.
     */
    firstInitialization( renderObject ) {

        const hasInitialized = this.renderObjects.has( renderObject );

        if ( hasInitialized === false ) {

            this.getRenderObjectData( renderObject );

            return true;

        }

        return false;

    }

    /**
     * Returns `true` if the current rendering produces motion vectors.
     *
     * @param {Renderer} renderer - The renderer.
     * @return {boolean} Whether the current rendering produces motion vectors or not.
     */
    needsVelocity( renderer ) {

        const mrt = renderer.getMRT();

        return ( mrt !== null && mrt.has( 'velocity' ) );

    }

    /**
     * Returns monitoring data for the given render object.
     *
     * @param {RenderObject} renderObject - The render object.
     * @return {Object} The monitoring data.
     */
    getRenderObjectData( renderObject ) {

        let data = this.renderObjects.get( renderObject );

        if ( data === undefined ) {

            const { geometry, material, object } = renderObject;

            data = {
                material: this.getMaterialData( material ),
                geometry: {
                    id: geometry.id,
                    attributes: this.getAttributesData( geometry.attributes ),
                    indexVersion: geometry.index ? geometry.index.version : null,
                    drawRange: { start: geometry.drawRange.start, count: geometry.drawRange.count }
                },
                worldMatrix: object.matrixWorld.clone()
            };

            if ( object.center ) {

                data.center = object.center.clone();

            }

            if ( object.morphTargetInfluences ) {

                data.morphTargetInfluences = object.morphTargetInfluences.slice();

            }

            if ( renderObject.bundle !== null ) {

                data.version = renderObject.bundle.version;

            }

            if ( data.material.transmission > 0 ) {

                const { width, height } = renderObject.context;

                data.bufferWidth = width;
                data.bufferHeight = height;

            }

            data.lights = this.getLightsData( renderObject.lightsNode.getLights() );

            this.renderObjects.set( renderObject, data );

        }

        return data;

    }

    /**
     * Returns an attribute data structure holding the attributes versions for
     * monitoring.
     *
     * @param {Object} attributes - The geometry attributes.
     * @return {Object} An object for monitoring the versions of attributes.
     */
    getAttributesData( attributes ) {

        const attributesData = {};

        for ( const name in attributes ) {

            const attribute = attributes[ name ];

            attributesData[ name ] = {
                version: attribute.version
            };

        }

        return attributesData;

    }

    /**
     * Returns `true` if the node builder's material uses
     * node properties.
     *
     * @param {NodeBuilder} builder - The current node builder.
     * @return {boolean} Whether the node builder's material uses node properties or not.
     */
    containsNode( builder ) {

        const material = builder.material;

        for ( const property in material ) {

            if ( material[ property ] && material[ property ].isNode )
                return true;

        }

        if ( builder.renderer.overrideNodes.modelViewMatrix !== null || builder.renderer.overrideNodes.modelNormalViewMatrix !== null )
            return true;

        return false;

    }

    /**
     * Returns a material data structure holding the material property values for
     * monitoring.
     *
     * @param {Material} material - The material.
     * @return {Object} An object for monitoring material properties.
     */
    getMaterialData( material ) {

        const data = {};

        for ( const property of this.refreshUniforms ) {

            const value = material[ property ];

            if ( value === null || value === undefined ) continue;

            if ( typeof value === 'object' && value.clone !== undefined ) {

                if ( value.isTexture === true ) {

                    data[ property ] = { id: value.id, version: value.version };

                } else {

                    data[ property ] = value.clone();

                }

            } else {

                data[ property ] = value;

            }

        }

        return data;

    }

    /**
     * Returns `true` if the given render object has not changed its state.
     *
     * @param {RenderObject} renderObject - The render object.
     * @param {Array<Light>} lightsData - The current material lights.
     * @return {boolean} Whether the given render object has changed its state or not.
     */
    equals( renderObject, lightsData ) {

        const { object, material, geometry } = renderObject;

        const renderObjectData = this.getRenderObjectData( renderObject );

        // world matrix

        if ( renderObjectData.worldMatrix.equals( object.matrixWorld ) !== true ) {

            renderObjectData.worldMatrix.copy( object.matrixWorld );

            return false;

        }

        // material

        const materialData = renderObjectData.material;

        for ( const property in materialData ) {

            const value = materialData[ property ];
            const mtlValue = material[ property ];

            if ( value.equals !== undefined ) {

                if ( value.equals( mtlValue ) === false ) {

                    value.copy( mtlValue );

                    return false;

                }

            } else if ( mtlValue.isTexture === true ) {

                if ( value.id !== mtlValue.id || value.version !== mtlValue.version ) {

                    value.id = mtlValue.id;
                    value.version = mtlValue.version;

                    return false;

                }

            } else if ( value !== mtlValue ) {

                materialData[ property ] = mtlValue;

                return false;

            }

        }

        if ( materialData.transmission > 0 ) {

            const { width, height } = renderObject.context;

            if ( renderObjectData.bufferWidth !== width || renderObjectData.bufferHeight !== height ) {

                renderObjectData.bufferWidth = width;
                renderObjectData.bufferHeight = height;

                return false;

            }

        }

        // geometry

        const storedGeometryData = renderObjectData.geometry;
        const attributes = geometry.attributes;
        const storedAttributes = storedGeometryData.attributes;

        const storedAttributeNames = Object.keys( storedAttributes );
        const currentAttributeNames = Object.keys( attributes );

        if ( storedGeometryData.id !== geometry.id ) {

            storedGeometryData.id = geometry.id;
            return false;

        }

        if ( storedAttributeNames.length !== currentAttributeNames.length ) {

            renderObjectData.geometry.attributes = this.getAttributesData( attributes );
            return false;

        }

        // compare each attribute

        for ( const name of storedAttributeNames ) {

            const storedAttributeData = storedAttributes[ name ];
            const attribute = attributes[ name ];

            if ( attribute === undefined ) {

                // attribute was removed
                delete storedAttributes[ name ];
                return false;

            }

            if ( storedAttributeData.version !== attribute.version ) {

                storedAttributeData.version = attribute.version;
                return false;

            }

        }

        // check index

        const index = geometry.index;
        const storedIndexVersion = storedGeometryData.indexVersion;
        const currentIndexVersion = index ? index.version : null;

        if ( storedIndexVersion !== currentIndexVersion ) {

            storedGeometryData.indexVersion = currentIndexVersion;
            return false;

        }

        // check drawRange

        if ( storedGeometryData.drawRange.start !== geometry.drawRange.start || storedGeometryData.drawRange.count !== geometry.drawRange.count ) {

            storedGeometryData.drawRange.start = geometry.drawRange.start;
            storedGeometryData.drawRange.count = geometry.drawRange.count;
            return false;

        }

        // morph targets

        if ( renderObjectData.morphTargetInfluences ) {

            let morphChanged = false;

            for ( let i = 0; i < renderObjectData.morphTargetInfluences.length; i ++ ) {

                if ( renderObjectData.morphTargetInfluences[ i ] !== object.morphTargetInfluences[ i ] ) {

                    morphChanged = true;

                }

            }

            if ( morphChanged ) return true;

        }

        // lights

        if ( renderObjectData.lights ) {

            for ( let i = 0; i < lightsData.length; i ++ ) {

                if ( renderObjectData.lights[ i ].map !== lightsData[ i ].map ) {

                    return false;

                }

            }

        }

        // center

        if ( renderObjectData.center ) {

            if ( renderObjectData.center.equals( object.center ) === false ) {

                renderObjectData.center.copy( object.center );

                return true;

            }

        }

        // bundle

        if ( renderObject.bundle !== null ) {

            renderObjectData.version = renderObject.bundle.version;

        }

        return true;

    }

    /**
     * Returns the lights data for the given material lights.
     *
     * @param {Array<Light>} materialLights - The material lights.
     * @return {Array<Object>} The lights data for the given material lights.
     */
    getLightsData( materialLights ) {

        const lights = [];

        for ( const light of materialLights ) {

            if ( light.isSpotLight === true && light.map !== null ) {

                // only add lights that have a map

                lights.push( { map: light.map.version } );

            }

        }

        return lights;

    }

    /**
     * Returns the lights for the given lights node and render ID.
     *
     * @param {LightsNode} lightsNode - The lights node.
     * @param {number} renderId - The render ID.
     * @return {Array} The lights for the given lights node and render ID.
     */
    getLights( lightsNode, renderId ) {

        if ( _lightsCache.has( lightsNode ) ) {

            const cached = _lightsCache.get( lightsNode );

            if ( cached.renderId === renderId ) {

                return cached.lightsData;

            }

        }

        const lightsData = this.getLightsData( lightsNode.getLights() );

        _lightsCache.set( lightsNode, { renderId, lightsData } );

        return lightsData;

    }

    /**
     * Checks if the given render object requires a refresh.
     *
     * @param {RenderObject} renderObject - The render object.
     * @param {NodeFrame} nodeFrame - The current node frame.
     * @return {boolean} Whether the given render object requires a refresh or not.
     */
    needsRefresh( renderObject, nodeFrame ) {

        if ( this.hasNode || this.hasAnimation || this.firstInitialization( renderObject ) || this.needsVelocity( nodeFrame.renderer ) )
            return true;

        const { renderId } = nodeFrame;

        if ( this.renderId !== renderId ) {

            this.renderId = renderId;

            return true;

        }

        const isStatic = renderObject.object.static === true;
        const isBundle = renderObject.bundle !== null && renderObject.bundle.static === true && this.getRenderObjectData( renderObject ).version === renderObject.bundle.version;

        if ( isStatic || isBundle )
            return false;

        const lightsData = this.getLights( renderObject.lightsNode, renderId );
        const notEqual = this.equals( renderObject, lightsData ) !== true;

        return notEqual;

    }

}

Methods

firstInitialization(renderObject: RenderObject): boolean
Code
firstInitialization( renderObject ) {

        const hasInitialized = this.renderObjects.has( renderObject );

        if ( hasInitialized === false ) {

            this.getRenderObjectData( renderObject );

            return true;

        }

        return false;

    }
needsVelocity(renderer: Renderer): boolean
Code
needsVelocity( renderer ) {

        const mrt = renderer.getMRT();

        return ( mrt !== null && mrt.has( 'velocity' ) );

    }
getRenderObjectData(renderObject: RenderObject): any
Code
getRenderObjectData( renderObject ) {

        let data = this.renderObjects.get( renderObject );

        if ( data === undefined ) {

            const { geometry, material, object } = renderObject;

            data = {
                material: this.getMaterialData( material ),
                geometry: {
                    id: geometry.id,
                    attributes: this.getAttributesData( geometry.attributes ),
                    indexVersion: geometry.index ? geometry.index.version : null,
                    drawRange: { start: geometry.drawRange.start, count: geometry.drawRange.count }
                },
                worldMatrix: object.matrixWorld.clone()
            };

            if ( object.center ) {

                data.center = object.center.clone();

            }

            if ( object.morphTargetInfluences ) {

                data.morphTargetInfluences = object.morphTargetInfluences.slice();

            }

            if ( renderObject.bundle !== null ) {

                data.version = renderObject.bundle.version;

            }

            if ( data.material.transmission > 0 ) {

                const { width, height } = renderObject.context;

                data.bufferWidth = width;
                data.bufferHeight = height;

            }

            data.lights = this.getLightsData( renderObject.lightsNode.getLights() );

            this.renderObjects.set( renderObject, data );

        }

        return data;

    }
getAttributesData(attributes: any): any
Code
getAttributesData( attributes ) {

        const attributesData = {};

        for ( const name in attributes ) {

            const attribute = attributes[ name ];

            attributesData[ name ] = {
                version: attribute.version
            };

        }

        return attributesData;

    }
containsNode(builder: NodeBuilder): boolean
Code
containsNode( builder ) {

        const material = builder.material;

        for ( const property in material ) {

            if ( material[ property ] && material[ property ].isNode )
                return true;

        }

        if ( builder.renderer.overrideNodes.modelViewMatrix !== null || builder.renderer.overrideNodes.modelNormalViewMatrix !== null )
            return true;

        return false;

    }
getMaterialData(material: Material): any
Code
getMaterialData( material ) {

        const data = {};

        for ( const property of this.refreshUniforms ) {

            const value = material[ property ];

            if ( value === null || value === undefined ) continue;

            if ( typeof value === 'object' && value.clone !== undefined ) {

                if ( value.isTexture === true ) {

                    data[ property ] = { id: value.id, version: value.version };

                } else {

                    data[ property ] = value.clone();

                }

            } else {

                data[ property ] = value;

            }

        }

        return data;

    }
equals(renderObject: RenderObject, lightsData: Light[]): boolean
Code
equals( renderObject, lightsData ) {

        const { object, material, geometry } = renderObject;

        const renderObjectData = this.getRenderObjectData( renderObject );

        // world matrix

        if ( renderObjectData.worldMatrix.equals( object.matrixWorld ) !== true ) {

            renderObjectData.worldMatrix.copy( object.matrixWorld );

            return false;

        }

        // material

        const materialData = renderObjectData.material;

        for ( const property in materialData ) {

            const value = materialData[ property ];
            const mtlValue = material[ property ];

            if ( value.equals !== undefined ) {

                if ( value.equals( mtlValue ) === false ) {

                    value.copy( mtlValue );

                    return false;

                }

            } else if ( mtlValue.isTexture === true ) {

                if ( value.id !== mtlValue.id || value.version !== mtlValue.version ) {

                    value.id = mtlValue.id;
                    value.version = mtlValue.version;

                    return false;

                }

            } else if ( value !== mtlValue ) {

                materialData[ property ] = mtlValue;

                return false;

            }

        }

        if ( materialData.transmission > 0 ) {

            const { width, height } = renderObject.context;

            if ( renderObjectData.bufferWidth !== width || renderObjectData.bufferHeight !== height ) {

                renderObjectData.bufferWidth = width;
                renderObjectData.bufferHeight = height;

                return false;

            }

        }

        // geometry

        const storedGeometryData = renderObjectData.geometry;
        const attributes = geometry.attributes;
        const storedAttributes = storedGeometryData.attributes;

        const storedAttributeNames = Object.keys( storedAttributes );
        const currentAttributeNames = Object.keys( attributes );

        if ( storedGeometryData.id !== geometry.id ) {

            storedGeometryData.id = geometry.id;
            return false;

        }

        if ( storedAttributeNames.length !== currentAttributeNames.length ) {

            renderObjectData.geometry.attributes = this.getAttributesData( attributes );
            return false;

        }

        // compare each attribute

        for ( const name of storedAttributeNames ) {

            const storedAttributeData = storedAttributes[ name ];
            const attribute = attributes[ name ];

            if ( attribute === undefined ) {

                // attribute was removed
                delete storedAttributes[ name ];
                return false;

            }

            if ( storedAttributeData.version !== attribute.version ) {

                storedAttributeData.version = attribute.version;
                return false;

            }

        }

        // check index

        const index = geometry.index;
        const storedIndexVersion = storedGeometryData.indexVersion;
        const currentIndexVersion = index ? index.version : null;

        if ( storedIndexVersion !== currentIndexVersion ) {

            storedGeometryData.indexVersion = currentIndexVersion;
            return false;

        }

        // check drawRange

        if ( storedGeometryData.drawRange.start !== geometry.drawRange.start || storedGeometryData.drawRange.count !== geometry.drawRange.count ) {

            storedGeometryData.drawRange.start = geometry.drawRange.start;
            storedGeometryData.drawRange.count = geometry.drawRange.count;
            return false;

        }

        // morph targets

        if ( renderObjectData.morphTargetInfluences ) {

            let morphChanged = false;

            for ( let i = 0; i < renderObjectData.morphTargetInfluences.length; i ++ ) {

                if ( renderObjectData.morphTargetInfluences[ i ] !== object.morphTargetInfluences[ i ] ) {

                    morphChanged = true;

                }

            }

            if ( morphChanged ) return true;

        }

        // lights

        if ( renderObjectData.lights ) {

            for ( let i = 0; i < lightsData.length; i ++ ) {

                if ( renderObjectData.lights[ i ].map !== lightsData[ i ].map ) {

                    return false;

                }

            }

        }

        // center

        if ( renderObjectData.center ) {

            if ( renderObjectData.center.equals( object.center ) === false ) {

                renderObjectData.center.copy( object.center );

                return true;

            }

        }

        // bundle

        if ( renderObject.bundle !== null ) {

            renderObjectData.version = renderObject.bundle.version;

        }

        return true;

    }
getLightsData(materialLights: Light[]): any[]
Code
getLightsData( materialLights ) {

        const lights = [];

        for ( const light of materialLights ) {

            if ( light.isSpotLight === true && light.map !== null ) {

                // only add lights that have a map

                lights.push( { map: light.map.version } );

            }

        }

        return lights;

    }
getLights(lightsNode: LightsNode, renderId: number): any[]
Code
getLights( lightsNode, renderId ) {

        if ( _lightsCache.has( lightsNode ) ) {

            const cached = _lightsCache.get( lightsNode );

            if ( cached.renderId === renderId ) {

                return cached.lightsData;

            }

        }

        const lightsData = this.getLightsData( lightsNode.getLights() );

        _lightsCache.set( lightsNode, { renderId, lightsData } );

        return lightsData;

    }
needsRefresh(renderObject: RenderObject, nodeFrame: NodeFrame): boolean
Code
needsRefresh( renderObject, nodeFrame ) {

        if ( this.hasNode || this.hasAnimation || this.firstInitialization( renderObject ) || this.needsVelocity( nodeFrame.renderer ) )
            return true;

        const { renderId } = nodeFrame;

        if ( this.renderId !== renderId ) {

            this.renderId = renderId;

            return true;

        }

        const isStatic = renderObject.object.static === true;
        const isBundle = renderObject.bundle !== null && renderObject.bundle.static === true && this.getRenderObjectData( renderObject ).version === renderObject.bundle.version;

        if ( isStatic || isBundle )
            return false;

        const lightsData = this.getLights( renderObject.lightsNode, renderId );
        const notEqual = this.equals( renderObject, lightsData ) !== true;

        return notEqual;

    }