UNPKG

three

Version:

JavaScript 3D library

194 lines (133 loc) • 4.67 kB
import { Material } from '../Material.js'; /** * A material instance that reuses the shader of a node material with its own properties. * All instances created from the same node material share a single shader build, so * configure the node material before creating instances. Instance values are read in * the shared node graph with `materialReference( name, type )`. Node properties such as * `colorNode` are read-only and always read from the node material. * * ```js * const nodeMaterial = new MeshBasicNodeMaterial(); * nodeMaterial.colorNode = materialReference( 'myColor', 'color' ); * * const material = new NodeMaterialInstance( nodeMaterial ); * material.myColor = new Color( 1, 0, 0 ); * ``` * * @augments Material */ class NodeMaterialInstance extends Material { /** * Constructs a new node material instance. * * @param {NodeMaterial} nodeMaterial - The node material defining the shared shader. * @throws {TypeError} When the given material is not a node material. */ constructor( nodeMaterial ) { super(); if ( nodeMaterial?.isNodeMaterial !== true ) { throw new TypeError( 'THREE.NodeMaterialInstance: The parameter must be a NodeMaterial.' ); } copyProperties( this, nodeMaterial ); // Instance values often live in `userData`, so each instance starts with its own copy. this.userData = cloneUserData( nodeMaterial.userData ); this.type = 'NodeMaterialInstance'; /** * This flag can be used for type testing. * * @type {boolean} * @readonly * @default true */ this.isNodeMaterialInstance = true; /** * The node material defining the shared shader. * * @type {NodeMaterial} */ this.nodeMaterial = nodeMaterial; } /** * Returns a cache key that identifies the shared shader, without traversing * the node graph for each instance. * * @return {string} The custom program cache key. */ customProgramCacheKey() { return this.nodeMaterial.uuid + ',' + this.nodeMaterial.version; } /** * Builds the shader of the node material while keeping this instance * as `builder.material`. * * @param {NodeBuilder} builder - The current node builder. */ build( builder ) { this.nodeMaterial.build( builder ); } /** * The version of the node material. Setting `needsUpdate` on the node material * updates all its instances, while setting it on an instance has no effect. * * @type {number} */ get version() { return this.nodeMaterial.version; } set version( value ) {} /** * Copies the values of the given node material instance to this instance. * Object values such as colors are shared with the source, except in `userData`. * Unlike {@link Material#copy}, `userData` is not copied via JSON: its first level * values are cloned when possible, so types such as colors are preserved. Textures * remain shared. * * @param {NodeMaterialInstance} source - The material to copy. * @return {NodeMaterialInstance} A reference to this instance. */ copy( source ) { copyProperties( this, source ); this.userData = cloneUserData( source.userData ); return this; } /** * Returns a new node material instance that shares the same node material. * * @return {NodeMaterialInstance} A clone of this instance. */ clone() { return new this.constructor( this.nodeMaterial ).copy( this ); } } // Read-only accessors shared by all instances, so node properties always reflect the node material. // They must be enumerable since the renderer detects node materials by iterating their properties. const _nodeDescriptors = {}; function getNodeDescriptor( property ) { return _nodeDescriptors[ property ] ??= { enumerable: true, get() { return this.nodeMaterial[ property ]; } }; } // Copies the first level of user data, cloning values such as colors and sharing textures. function cloneUserData( userData ) { const result = {}; for ( const key in userData ) { const value = userData[ key ]; result[ key ] = value?.clone !== undefined && value.isTexture !== true ? value.clone() : value; } return result; } // Copies configuration while preserving the target's identity and events. function copyProperties( target, source ) { for ( const property of Object.keys( source ) ) { if ( property === 'uuid' || property === 'version' || property === '_listeners' ) continue; if ( property.endsWith( 'Node' ) ) { if ( Object.hasOwn( target, property ) === false ) Object.defineProperty( target, property, getNodeDescriptor( property ) ); } else { target[ property ] = source[ property ]; } } } export default NodeMaterialInstance;