THREE.extendMaterial = THREE.ShaderMaterial.extend = function () { const Materials = [ THREE.ShadowMaterial, THREE.SpriteMaterial, THREE.RawShaderMaterial, THREE.ShaderMaterial, THREE.PointsMaterial, THREE.MeshPhysicalMaterial, THREE.MeshStandardMaterial, THREE.MeshPhongMaterial, THREE.MeshToonMaterial, THREE.MeshNormalMaterial, THREE.MeshLambertMaterial, THREE.MeshDepthMaterial, //THREE.MeshDistanceMaterial, THREE.MeshBasicMaterial, //THREE.MeshMatcapMaterial, THREE.LineDashedMaterial, THREE.LineBasicMaterial, THREE.Material, THREE.MeshFaceMaterial, THREE.MultiMaterial, THREE.PointCloudMaterial, THREE.ParticleBasicMaterial, THREE.ParticleSystemMaterial ]; // Type on prototype needed to identify when minified THREE.ShadowMaterial.prototype.type = 'ShadowMaterial'; THREE.SpriteMaterial.prototype.type = 'SpriteMaterial'; THREE.RawShaderMaterial.prototype.type = 'RawShaderMaterial'; THREE.ShaderMaterial.prototype.type = 'ShaderMaterial'; THREE.PointsMaterial.prototype.type = 'PointsMaterial'; THREE.MeshPhysicalMaterial.prototype.type = 'MeshPhysicalMaterial'; THREE.MeshStandardMaterial.prototype.type = 'MeshStandardMaterial'; THREE.MeshPhongMaterial.prototype.type = 'MeshPhongMaterial'; THREE.MeshToonMaterial.prototype.type = 'MeshToonMaterial'; THREE.MeshNormalMaterial.prototype.type = 'MeshNormalMaterial'; THREE.MeshLambertMaterial.prototype.type = 'MeshLambertMaterial'; THREE.MeshDepthMaterial.prototype.type = 'MeshDepthMaterial'; if ( THREE.MeshDistanceMaterial ) THREE.MeshDistanceMaterial.prototype.type = 'MeshDistanceMaterial'; THREE.MeshBasicMaterial.prototype.type = 'MeshBasicMaterial'; if ( THREE.MeshMatcapMaterial ) THREE.MeshMatcapMaterial.prototype.type = 'MeshMatcapMaterial'; THREE.LineDashedMaterial.prototype.type = 'LineDashedMaterial'; THREE.LineBasicMaterial.prototype.type = 'LineBasicMaterial'; THREE.Material.prototype.type = 'Material'; THREE.MeshFaceMaterial.prototype.type = 'MeshFaceMaterial'; THREE.MultiMaterial.prototype.type = 'MultiMaterial'; THREE.PointCloudMaterial.prototype.type = 'PointCloudMaterial'; THREE.ParticleBasicMaterial.prototype.type = 'ParticleBasicMaterial'; THREE.ParticleSystemMaterial.prototype.type = 'ParticleSystemMaterial'; for ( let constructor of Materials ) constructor.prototype.templates = []; const mappings = { MeshLambertMaterial: { id: 'meshlambert', name: 'lambert' }, MeshBasicMaterial: { id: 'meshbasic', name: 'basic' }, MeshStandardMaterial: { id: 'meshphysical', name: 'physical' }, MeshPhongMaterial: { id: 'meshphong', name: 'phong' }, MeshMatcapMaterial: { id: 'meshmatcap', name: 'matcap' }, PointsMaterial: { id: 'points', name: 'points' }, LineDashedMaterial: { id: 'dashed', name: 'linedashed' }, MeshDepthMaterial: { id: 'depth', name: 'depth' }, MeshNormalMaterial: { id: 'normal', name: 'normal' }, MeshDistanceMaterial: { id: 'distanceRGBA', name: 'distanceRGBA' }, SpriteMaterial: { id: 'sprite', name: 'sprite' } }; const uniformFlags = { alphaTest: { as: 'ALPHATEST', not: 0 } }; const mapFlags = { map: 'USE_MAP', aoMap: 'USE_AOMAP', envMap: 'USE_ENVMAP', bumpMap: 'USE_BUMPMAP', normalMap: 'USE_NORMALMAP', lightMap: 'USE_LIGHTMAP', emissiveMap: 'USE_EMISSIVEMAP', specularMap: 'USE_SPECULARMAP', roughnessMap: 'USE_ROUGHNESSMAP', metalnessMap: 'USE_METALNESSMAP', alphaMap: 'USE_ALPHAMAP', displacementMap: 'USE_DISPLACEMENTMAP' }; function cloneUniform( src, dst ) { dst = dst || {}; for ( let key in src ) { const property = src[ key ]; if ( property && ( property.isColor || property.isMatrix3 || property.isMatrix4 || property.isVector2 || property.isVector3 || property.isVector4 || property.isTexture ) ) { dst[ key ] = property.clone(); } else if ( Array.isArray( property ) ) { dst[ key ] = property.slice(); } else { dst[ key ] = property; } } return dst; } function makeUniform( value ) { return value && value.value !== undefined ? value : { value : value }; } function cloneUniforms( src, dst, notNull = false ) { dst = {}; for ( let u in src ) { const uniform = src[ u ]; if ( notNull && ( uniform.value === null || uniform.value === 0 ) ) continue; if ( uniform.shared ) { dst[ u ] = uniform; } else { dst[ u ] = cloneUniform( uniform ); } } return dst; } function applyPatches( chunk, map ) { for ( let name in map ) { const value = map[ name ]; if ( value instanceof Object ) { if ( THREE.ShaderChunk[ name ] === undefined ) { console.error( 'THREE.ShaderMaterial.extend: ShaderChunk "%s" not found', name ); } else { chunk = chunk.replace( '#include <' + name + '>', applyPatches( THREE.ShaderChunk[ name ], value ) ); } } else { if ( name[ 0 ] === '@' ) { // Replace const line = name.substr( 1 ); chunk = chunk.replace( line, value ); } else if ( name[ 0 ] === '?' ) { // Insert before const line = name.substr( 1 ); chunk = chunk.replace( line, value + '\n' + line ); } else { // Insert after if ( !chunk ) { console.error( "THREE.patchShader: chunk not found '%s'", name ); } else { chunk = chunk.replace( name, name + '\n' + value ); } } } } return chunk; } function applyConstants( name, uniform, defines ) { // Maps require USE_X constants if ( mapFlags[ name ] !== undefined && uniform && uniform.value !== undefined ) { // Expose uniform to be detected // defines[ mapFlags[ name ] ] = true; instance[ name ] = uniform.value; } // Converts properties like alphaTest to their constant const flag = uniformFlags[ name ]; if ( flag !== undefined && ( flag.not === undefined || flag.not !== value ) ) defines[ flag.as ] = uniform.value; } // Uniform mixers function applyUniforms( src, dst, defines ) { // Apply is used as initial definition if ( !src.uniforms ) return; for ( let name in src.uniforms ) { if ( !dst.uniforms[ name ] ) dst.uniforms[ name ] = {}; // Accepts uniform objects and plain values let uniform = makeUniform( src.uniforms[ name ] ); dst.uniforms[ name ] = uniform; if ( defines ) applyConstants( name, uniform, defines ); } } function mixUniforms( src, dst, defines ) { // Only mixed uniforms are passed to dst, only if they not exist if ( !src.uniforms ) return; for ( let name in src.uniforms ) { let uniform = src.uniforms[ name ]; uniform = makeUniform( uniform ); if ( uniform.mixed && dst.uniforms[ name ] === undefined ) { dst.uniforms[ name ] = uniform.shared ? uniform : cloneUniform( uniform ); if ( defines ) applyConstants( name, uniform, defines ); } } } function mapUniforms( name, uniforms, object ) { if ( object.explicit === false ) { // Use all possible uniforms even if not declared return cloneUniforms( THREE.ShaderLib[ name ].uniforms, uniforms ); } else { // Only use declared and necessary return cloneUniforms( THREE.ShaderLib[ name ].uniforms, uniforms, true ); } } function mapShader ( name, type ) { const mapping = mappings[ name ]; return THREE.ShaderChunk[ mapping.id + '_' + ( type === 'vertex' ? 'vert' : 'frag' ) ]; } function patchShader ( shader, object, uniformsMixer = applyUniforms, defines = null ) { // A shared header ( varyings, uniforms, functions etc ) let header = ( object.header || '' ) + '\n'; let vertexShader = ( object.vertexHeader || '' ) + '\n' + shader.vertexShader; let fragmentShader = ( object.fragmentHeader || '' ) + '\n' + shader.fragmentShader; if ( object.vertexEnd ) vertexShader = vertexShader.replace( /\}(?=[^.]*$)/g, object.vertexEnd + '\n}' ); if ( object.fragmentEnd ) fragmentShader = fragmentShader.replace( /\}(?=[^.]*$)/g, object.fragmentEnd + '\n}' ); // Insert or replace lines (@ to replace) if ( object.vertex !== undefined ) vertexShader = applyPatches( vertexShader, object.vertex ); if ( object.fragment !== undefined ) fragmentShader = applyPatches( fragmentShader, object.fragment ); shader.vertexShader = header + vertexShader; shader.fragmentShader = header + fragmentShader; if ( uniformsMixer instanceof Function ) uniformsMixer( object, shader, defines ); return shader; } THREE.cloneUniforms = cloneUniforms; THREE.patchShader = patchShader; THREE.mapShader = mapShader; const _clone = THREE.ShaderMaterial.prototype.clone; THREE.ShaderMaterial.prototype.clone = function() { const clone = _clone.call( this ); clone.templates = this.templates; return clone; }; return function ( source, object ) { object = object || {}; // Extend from class or shader material let uniforms = {}, vertexShader = '', fragmentShader = ''; // Inherit from previous material templates chain const base = object.template || object.extends; // New shader material const material = new THREE.ShaderMaterial; const properties = object.material || {}; const defines = Object.assign( {}, properties.defines ); // Create new template chain material.templates = [ object ]; if ( source.templates instanceof Array ) material.templates = source.templates.concat( material.templates ); if ( source instanceof Function ) { // Source is a constructor const name = source.prototype.type; const mapping = mappings[ name ]; if ( mapping === undefined ) { console.error( 'THREE.extendMaterial: no mapping for material class "%s" found', name ); return material; } uniforms = mapUniforms( mapping.name, uniforms, object ); // Use only declared/necessary uniforms or all vertexShader = THREE.ShaderChunk[ mapping.id + '_vert' ]; fragmentShader = THREE.ShaderChunk[ mapping.id + '_frag' ]; properties.lights = properties.lights === undefined ? true : properties.lights; } else if ( source.isShaderMaterial ) { // Source is a ShaderMaterial uniforms = cloneUniforms( source.uniforms, uniforms ); // Merge uniforms as vertexShader = source.vertexShader; fragmentShader = source.fragmentShader; material.copy( source ); if ( source.defines ) Object.assign( defines, source.defines ); } else { // Source is a material instance const name = source.type; const mapping = mappings[ name ]; if ( mapping === undefined ) { console.error( 'THREE.extendMaterial: no mapping for material class "%s" found', name ); return material; } uniforms = mapUniforms( mapping.name, uniforms, object ); vertexShader = THREE.ShaderChunk[ mapping.id + '_vert' ]; fragmentShader = THREE.ShaderChunk[ mapping.id + '_frag' ]; properties.lights = properties.lights === undefined ? true : properties.lights; // Apply properties to uniforms for ( let name in uniforms ) if ( source[ name ] !== undefined ) uniforms[ name ].value = source[ name ]; } // Override constants if ( object.defines ) Object.assign( defines, object.defines ); // A shared header ( varyings, uniforms, functions etc ) let header = ( object.header || '' ) + '\n'; // Insert or replace lines (@ to replace) if ( object.vertex !== undefined ) vertexShader = applyPatches( vertexShader, object.vertex ); if ( object.fragment !== undefined ) fragmentShader = applyPatches( fragmentShader, object.fragment ); properties.defines = defines; properties.uniforms = uniforms; properties.vertexShader = header + ( object.vertexHeader || '' ) + '\n' + vertexShader; properties.fragmentShader = header + ( object.fragmentHeader || '' ) + '\n' + fragmentShader; if ( object.vertexEnd ) properties.vertexShader = properties.vertexShader.replace( /\}(?=[^.]*$)/g, object.vertexEnd + '\n}' ); if ( object.fragmentEnd ) properties.fragmentShader = properties.fragmentShader.replace( /\}(?=[^.]*$)/g, object.fragmentEnd + '\n}' ); // Assign uniforms applyUniforms( object, properties, defines ); // Uniforms override if ( object.overrideUniforms ) { for ( let name in object.overrideUniforms ) { if ( uniforms[ name ] !== undefined ) { const src = object.overrideUniforms[ name ]; const dst = uniforms[ name ]; for ( let k in src ) dst[ k ] = src[ k ]; // Expose mixed uniform to template ( material before might have been built-in ) if ( dst.mixed ) { if ( !object.uniforms ) object.uniforms = {}; object.uniforms[ name ] = dst; } } } } // Apply a base template, uniforms that were required in a template must be flagged as mixed if ( base && base.templates && base.templates.length ) { for ( let template of base.templates ) patchShader( properties, template, mixUniforms, defines ); } if ( uniforms.envMap && uniforms.envMap.value ) properties.envMap = uniforms.envMap.value; // Finally apply material properties material.setValues( properties ); // Fix: since we use #ifdef false would be false positive for ( let name in defines ) if ( defines[ name ] === false ) delete defines[ name ]; return material; } }();