import Cartesian2 from "../Core/Cartesian2.js"; import clone from "../Core/clone.js"; import Color from "../Core/Color.js"; import combine from "../Core/combine.js"; import createGuid from "../Core/createGuid.js"; import defaultValue from "../Core/defaultValue.js"; import defined from "../Core/defined.js"; import destroyObject from "../Core/destroyObject.js"; import DeveloperError from "../Core/DeveloperError.js"; import loadKTX2 from "../Core/loadKTX2.js"; import Matrix2 from "../Core/Matrix2.js"; import Matrix3 from "../Core/Matrix3.js"; import Matrix4 from "../Core/Matrix4.js"; import Resource from "../Core/Resource.js"; import CubeMap from "../Renderer/CubeMap.js"; import Texture from "../Renderer/Texture.js"; import AspectRampMaterial from "../Shaders/Materials/AspectRampMaterial.js"; import BumpMapMaterial from "../Shaders/Materials/BumpMapMaterial.js"; import CheckerboardMaterial from "../Shaders/Materials/CheckerboardMaterial.js"; import DotMaterial from "../Shaders/Materials/DotMaterial.js"; import ElevationBandMaterial from "../Shaders/Materials/ElevationBandMaterial.js"; import ElevationContourMaterial from "../Shaders/Materials/ElevationContourMaterial.js"; import ElevationRampMaterial from "../Shaders/Materials/ElevationRampMaterial.js"; import FadeMaterial from "../Shaders/Materials/FadeMaterial.js"; import GridMaterial from "../Shaders/Materials/GridMaterial.js"; import NormalMapMaterial from "../Shaders/Materials/NormalMapMaterial.js"; import PolylineArrowMaterial from "../Shaders/Materials/PolylineArrowMaterial.js"; import PolylineDashMaterial from "../Shaders/Materials/PolylineDashMaterial.js"; import PolylineGlowMaterial from "../Shaders/Materials/PolylineGlowMaterial.js"; import PolylineOutlineMaterial from "../Shaders/Materials/PolylineOutlineMaterial.js"; import RimLightingMaterial from "../Shaders/Materials/RimLightingMaterial.js"; import Sampler from "../Renderer/Sampler.js"; import SlopeRampMaterial from "../Shaders/Materials/SlopeRampMaterial.js"; import StripeMaterial from "../Shaders/Materials/StripeMaterial.js"; import TextureMagnificationFilter from "../Renderer/TextureMagnificationFilter.js"; import TextureMinificationFilter from "../Renderer/TextureMinificationFilter.js"; import WaterMaterial from "../Shaders/Materials/Water.js"; /** * A Material defines surface appearance through a combination of diffuse, specular, * normal, emission, and alpha components. These values are specified using a * JSON schema called Fabric which gets parsed and assembled into glsl shader code * behind-the-scenes. Check out the {@link https://github.com/CesiumGS/cesium/wiki/Fabric|wiki page} * for more details on Fabric. *

* * * Base material types and their uniforms: *
* * *
* * @alias Material * * @param {Object} [options] Object with the following properties: * @param {Boolean} [options.strict=false] Throws errors for issues that would normally be ignored, including unused uniforms or materials. * @param {Boolean|Function} [options.translucent=true] When true or a function that returns true, the geometry * with this material is expected to appear translucent. * @param {TextureMinificationFilter} [options.minificationFilter=TextureMinificationFilter.LINEAR] The {@link TextureMinificationFilter} to apply to this material's textures. * @param {TextureMagnificationFilter} [options.magnificationFilter=TextureMagnificationFilter.LINEAR] The {@link TextureMagnificationFilter} to apply to this material's textures. * @param {Object} options.fabric The fabric JSON used to generate the material. * * @constructor * * @exception {DeveloperError} fabric: uniform has invalid type. * @exception {DeveloperError} fabric: uniforms and materials cannot share the same property. * @exception {DeveloperError} fabric: cannot have source and components in the same section. * @exception {DeveloperError} fabric: property name is not valid. It should be 'type', 'materials', 'uniforms', 'components', or 'source'. * @exception {DeveloperError} fabric: property name is not valid. It should be 'diffuse', 'specular', 'shininess', 'normal', 'emission', or 'alpha'. * @exception {DeveloperError} strict: shader source does not use string. * @exception {DeveloperError} strict: shader source does not use uniform. * @exception {DeveloperError} strict: shader source does not use material. * * @see {@link https://github.com/CesiumGS/cesium/wiki/Fabric|Fabric wiki page} for a more detailed options of Fabric. * * @demo {@link https://sandcastle.cesium.com/index.html?src=Materials.html|Cesium Sandcastle Materials Demo} * * @example * // Create a color material with fromType: * polygon.material = Cesium.Material.fromType('Color'); * polygon.material.uniforms.color = new Cesium.Color(1.0, 1.0, 0.0, 1.0); * * // Create the default material: * polygon.material = new Cesium.Material(); * * // Create a color material with full Fabric notation: * polygon.material = new Cesium.Material({ * fabric : { * type : 'Color', * uniforms : { * color : new Cesium.Color(1.0, 1.0, 0.0, 1.0) * } * } * }); */ function Material(options) { /** * The material type. Can be an existing type or a new type. If no type is specified in fabric, type is a GUID. * @type {String} * @default undefined */ this.type = undefined; /** * The glsl shader source for this material. * @type {String} * @default undefined */ this.shaderSource = undefined; /** * Maps sub-material names to Material objects. * @type {Object} * @default undefined */ this.materials = undefined; /** * Maps uniform names to their values. * @type {Object} * @default undefined */ this.uniforms = undefined; this._uniforms = undefined; /** * When true or a function that returns true, * the geometry is expected to appear translucent. * @type {Boolean|Function} * @default undefined */ this.translucent = undefined; this._minificationFilter = defaultValue( options.minificationFilter, TextureMinificationFilter.LINEAR ); this._magnificationFilter = defaultValue( options.magnificationFilter, TextureMagnificationFilter.LINEAR ); this._strict = undefined; this._template = undefined; this._count = undefined; this._texturePaths = {}; this._loadedImages = []; this._loadedCubeMaps = []; this._textures = {}; this._updateFunctions = []; this._defaultTexture = undefined; initializeMaterial(options, this); Object.defineProperties(this, { type: { value: this.type, writable: false, }, }); if (!defined(Material._uniformList[this.type])) { Material._uniformList[this.type] = Object.keys(this._uniforms); } } // Cached list of combined uniform names indexed by type. // Used to get the list of uniforms in the same order. Material._uniformList = {}; /** * Creates a new material using an existing material type. *

* Shorthand for: new Material({fabric : {type : type}}); * * @param {String} type The base material type. * @param {Object} [uniforms] Overrides for the default uniforms. * @returns {Material} New material object. * * @exception {DeveloperError} material with that type does not exist. * * @example * const material = Cesium.Material.fromType('Color', { * color : new Cesium.Color(1.0, 0.0, 0.0, 1.0) * }); */ Material.fromType = function (type, uniforms) { //>>includeStart('debug', pragmas.debug); if (!defined(Material._materialCache.getMaterial(type))) { throw new DeveloperError(`material with type '${type}' does not exist.`); } //>>includeEnd('debug'); const material = new Material({ fabric: { type: type, }, }); if (defined(uniforms)) { for (const name in uniforms) { if (uniforms.hasOwnProperty(name)) { material.uniforms[name] = uniforms[name]; } } } return material; }; /** * Gets whether or not this material is translucent. * @returns {Boolean} true if this material is translucent, false otherwise. */ Material.prototype.isTranslucent = function () { if (defined(this.translucent)) { if (typeof this.translucent === "function") { return this.translucent(); } return this.translucent; } let translucent = true; const funcs = this._translucentFunctions; const length = funcs.length; for (let i = 0; i < length; ++i) { const func = funcs[i]; if (typeof func === "function") { translucent = translucent && func(); } else { translucent = translucent && func; } if (!translucent) { break; } } return translucent; }; /** * @private */ Material.prototype.update = function (context) { this._defaultTexture = context.defaultTexture; let i; let uniformId; const loadedImages = this._loadedImages; let length = loadedImages.length; for (i = 0; i < length; ++i) { const loadedImage = loadedImages[i]; uniformId = loadedImage.id; let image = loadedImage.image; // Images transcoded from KTX2 can contain multiple mip levels: // https://github.khronos.org/KTX-Specification/#_mip_level_array let mipLevels; if (Array.isArray(image)) { // highest detail mip should be level 0 mipLevels = image.slice(1, image.length).map(function (mipLevel) { return mipLevel.bufferView; }); image = image[0]; } const sampler = new Sampler({ minificationFilter: this._minificationFilter, magnificationFilter: this._magnificationFilter, }); let texture; if (defined(image.internalFormat)) { texture = new Texture({ context: context, pixelFormat: image.internalFormat, width: image.width, height: image.height, source: { arrayBufferView: image.bufferView, mipLevels: mipLevels, }, sampler: sampler, }); } else { texture = new Texture({ context: context, source: image, sampler: sampler, }); } // The material destroys its old texture only after the new one has been loaded. // This will ensure a smooth swap of textures and prevent the default texture // from appearing for a few frames. const oldTexture = this._textures[uniformId]; if (defined(oldTexture) && oldTexture !== this._defaultTexture) { oldTexture.destroy(); } this._textures[uniformId] = texture; const uniformDimensionsName = `${uniformId}Dimensions`; if (this.uniforms.hasOwnProperty(uniformDimensionsName)) { const uniformDimensions = this.uniforms[uniformDimensionsName]; uniformDimensions.x = texture._width; uniformDimensions.y = texture._height; } } loadedImages.length = 0; const loadedCubeMaps = this._loadedCubeMaps; length = loadedCubeMaps.length; for (i = 0; i < length; ++i) { const loadedCubeMap = loadedCubeMaps[i]; uniformId = loadedCubeMap.id; const images = loadedCubeMap.images; const cubeMap = new CubeMap({ context: context, source: { positiveX: images[0], negativeX: images[1], positiveY: images[2], negativeY: images[3], positiveZ: images[4], negativeZ: images[5], }, sampler: new Sampler({ minificationFilter: this._minificationFilter, magnificationFilter: this._magnificationFilter, }), }); this._textures[uniformId] = cubeMap; } loadedCubeMaps.length = 0; const updateFunctions = this._updateFunctions; length = updateFunctions.length; for (i = 0; i < length; ++i) { updateFunctions[i](this, context); } const subMaterials = this.materials; for (const name in subMaterials) { if (subMaterials.hasOwnProperty(name)) { subMaterials[name].update(context); } } }; /** * Returns true if this object was destroyed; otherwise, false. *

* If this object was destroyed, it should not be used; calling any function other than * isDestroyed will result in a {@link DeveloperError} exception. * * @returns {Boolean} True if this object was destroyed; otherwise, false. * * @see Material#destroy */ Material.prototype.isDestroyed = function () { return false; }; /** * Destroys the WebGL resources held by this object. Destroying an object allows for deterministic * release of WebGL resources, instead of relying on the garbage collector to destroy this object. *

* Once an object is destroyed, it should not be used; calling any function other than * isDestroyed will result in a {@link DeveloperError} exception. Therefore, * assign the return value (undefined) to the object as done in the example. * * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called. * * * @example * material = material && material.destroy(); * * @see Material#isDestroyed */ Material.prototype.destroy = function () { const textures = this._textures; for (const texture in textures) { if (textures.hasOwnProperty(texture)) { const instance = textures[texture]; if (instance !== this._defaultTexture) { instance.destroy(); } } } const materials = this.materials; for (const material in materials) { if (materials.hasOwnProperty(material)) { materials[material].destroy(); } } return destroyObject(this); }; function initializeMaterial(options, result) { options = defaultValue(options, defaultValue.EMPTY_OBJECT); result._strict = defaultValue(options.strict, false); result._count = defaultValue(options.count, 0); result._template = clone( defaultValue(options.fabric, defaultValue.EMPTY_OBJECT) ); result._template.uniforms = clone( defaultValue(result._template.uniforms, defaultValue.EMPTY_OBJECT) ); result._template.materials = clone( defaultValue(result._template.materials, defaultValue.EMPTY_OBJECT) ); result.type = defined(result._template.type) ? result._template.type : createGuid(); result.shaderSource = ""; result.materials = {}; result.uniforms = {}; result._uniforms = {}; result._translucentFunctions = []; let translucent; // If the cache contains this material type, build the material template off of the stored template. const cachedMaterial = Material._materialCache.getMaterial(result.type); if (defined(cachedMaterial)) { const template = clone(cachedMaterial.fabric, true); result._template = combine(result._template, template, true); translucent = cachedMaterial.translucent; } // Make sure the template has no obvious errors. More error checking happens later. checkForTemplateErrors(result); // If the material has a new type, add it to the cache. if (!defined(cachedMaterial)) { Material._materialCache.addMaterial(result.type, result); } createMethodDefinition(result); createUniforms(result); createSubMaterials(result); const defaultTranslucent = result._translucentFunctions.length === 0 ? true : undefined; translucent = defaultValue(translucent, defaultTranslucent); translucent = defaultValue(options.translucent, translucent); if (defined(translucent)) { if (typeof translucent === "function") { const wrappedTranslucent = function () { return translucent(result); }; result._translucentFunctions.push(wrappedTranslucent); } else { result._translucentFunctions.push(translucent); } } } function checkForValidProperties(object, properties, result, throwNotFound) { if (defined(object)) { for (const property in object) { if (object.hasOwnProperty(property)) { const hasProperty = properties.indexOf(property) !== -1; if ( (throwNotFound && !hasProperty) || (!throwNotFound && hasProperty) ) { result(property, properties); } } } } } function invalidNameError(property, properties) { //>>includeStart('debug', pragmas.debug); let errorString = `fabric: property name '${property}' is not valid. It should be `; for (let i = 0; i < properties.length; i++) { const propertyName = `'${properties[i]}'`; errorString += i === properties.length - 1 ? `or ${propertyName}.` : `${propertyName}, `; } throw new DeveloperError(errorString); //>>includeEnd('debug'); } function duplicateNameError(property, properties) { //>>includeStart('debug', pragmas.debug); const errorString = `fabric: uniforms and materials cannot share the same property '${property}'`; throw new DeveloperError(errorString); //>>includeEnd('debug'); } const templateProperties = [ "type", "materials", "uniforms", "components", "source", ]; const componentProperties = [ "diffuse", "specular", "shininess", "normal", "emission", "alpha", ]; function checkForTemplateErrors(material) { const template = material._template; const uniforms = template.uniforms; const materials = template.materials; const components = template.components; // Make sure source and components do not exist in the same template. //>>includeStart('debug', pragmas.debug); if (defined(components) && defined(template.source)) { throw new DeveloperError( "fabric: cannot have source and components in the same template." ); } //>>includeEnd('debug'); // Make sure all template and components properties are valid. checkForValidProperties(template, templateProperties, invalidNameError, true); checkForValidProperties( components, componentProperties, invalidNameError, true ); // Make sure uniforms and materials do not share any of the same names. const materialNames = []; for (const property in materials) { if (materials.hasOwnProperty(property)) { materialNames.push(property); } } checkForValidProperties(uniforms, materialNames, duplicateNameError, false); } function isMaterialFused(shaderComponent, material) { const materials = material._template.materials; for (const subMaterialId in materials) { if (materials.hasOwnProperty(subMaterialId)) { if (shaderComponent.indexOf(subMaterialId) > -1) { return true; } } } return false; } // Create the czm_getMaterial method body using source or components. function createMethodDefinition(material) { const components = material._template.components; const source = material._template.source; if (defined(source)) { material.shaderSource += `${source}\n`; } else { material.shaderSource += "czm_material czm_getMaterial(czm_materialInput materialInput)\n{\n"; material.shaderSource += "czm_material material = czm_getDefaultMaterial(materialInput);\n"; if (defined(components)) { const isMultiMaterial = Object.keys(material._template.materials).length > 0; for (const component in components) { if (components.hasOwnProperty(component)) { if (component === "diffuse" || component === "emission") { const isFusion = isMultiMaterial && isMaterialFused(components[component], material); const componentSource = isFusion ? components[component] : `czm_gammaCorrect(${components[component]})`; material.shaderSource += `material.${component} = ${componentSource}; \n`; } else if (component === "alpha") { material.shaderSource += `material.alpha = ${components.alpha}; \n`; } else { material.shaderSource += `material.${component} = ${components[component]};\n`; } } } } material.shaderSource += "return material;\n}\n"; } } const matrixMap = { mat2: Matrix2, mat3: Matrix3, mat4: Matrix4, }; const ktx2Regex = /\.ktx2$/i; function createTexture2DUpdateFunction(uniformId) { let oldUniformValue; return function (material, context) { const uniforms = material.uniforms; const uniformValue = uniforms[uniformId]; const uniformChanged = oldUniformValue !== uniformValue; const uniformValueIsDefaultImage = !defined(uniformValue) || uniformValue === Material.DefaultImageId; oldUniformValue = uniformValue; let texture = material._textures[uniformId]; let uniformDimensionsName; let uniformDimensions; if (uniformValue instanceof HTMLVideoElement) { // HTMLVideoElement.readyState >=2 means we have enough data for the current frame. // See: https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/readyState if (uniformValue.readyState >= 2) { if (uniformChanged && defined(texture)) { if (texture !== context.defaultTexture) { texture.destroy(); } texture = undefined; } if (!defined(texture) || texture === context.defaultTexture) { const sampler = new Sampler({ minificationFilter: material._minificationFilter, magnificationFilter: material._magnificationFilter, }); texture = new Texture({ context: context, source: uniformValue, sampler: sampler, }); material._textures[uniformId] = texture; return; } texture.copyFrom({ source: uniformValue, }); } else if (!defined(texture)) { material._textures[uniformId] = context.defaultTexture; } return; } if (uniformValue instanceof Texture && uniformValue !== texture) { material._texturePaths[uniformId] = undefined; const tmp = material._textures[uniformId]; if (defined(tmp) && tmp !== material._defaultTexture) { tmp.destroy(); } material._textures[uniformId] = uniformValue; uniformDimensionsName = `${uniformId}Dimensions`; if (uniforms.hasOwnProperty(uniformDimensionsName)) { uniformDimensions = uniforms[uniformDimensionsName]; uniformDimensions.x = uniformValue._width; uniformDimensions.y = uniformValue._height; } return; } if (uniformChanged && defined(texture) && uniformValueIsDefaultImage) { // If the newly-assigned texture is the default texture, // we don't need to wait for a new image to load before destroying // the old texture. if (texture !== material._defaultTexture) { texture.destroy(); } texture = undefined; } if (!defined(texture)) { material._texturePaths[uniformId] = undefined; texture = material._textures[uniformId] = material._defaultTexture; uniformDimensionsName = `${uniformId}Dimensions`; if (uniforms.hasOwnProperty(uniformDimensionsName)) { uniformDimensions = uniforms[uniformDimensionsName]; uniformDimensions.x = texture._width; uniformDimensions.y = texture._height; } } if (uniformValueIsDefaultImage) { return; } // When using the entity layer, the Resource objects get recreated on getValue because // they are clonable. That's why we check the url property for Resources // because the instances aren't the same and we keep trying to load the same // image if it fails to load. const isResource = uniformValue instanceof Resource; if ( !defined(material._texturePaths[uniformId]) || (isResource && uniformValue.url !== material._texturePaths[uniformId].url) || (!isResource && uniformValue !== material._texturePaths[uniformId]) ) { if (typeof uniformValue === "string" || isResource) { const resource = isResource ? uniformValue : Resource.createIfNeeded(uniformValue); let promise; if (ktx2Regex.test(resource.url)) { promise = loadKTX2(resource.url); } else { promise = resource.fetchImage(); } Promise.resolve(promise) .then(function (image) { material._loadedImages.push({ id: uniformId, image: image, }); }) .catch(function () { if (defined(texture) && texture !== material._defaultTexture) { texture.destroy(); } material._textures[uniformId] = material._defaultTexture; }); } else if ( uniformValue instanceof HTMLCanvasElement || uniformValue instanceof HTMLImageElement ) { material._loadedImages.push({ id: uniformId, image: uniformValue, }); } material._texturePaths[uniformId] = uniformValue; } }; } function createCubeMapUpdateFunction(uniformId) { return function (material, context) { const uniformValue = material.uniforms[uniformId]; if (uniformValue instanceof CubeMap) { const tmp = material._textures[uniformId]; if (tmp !== material._defaultTexture) { tmp.destroy(); } material._texturePaths[uniformId] = undefined; material._textures[uniformId] = uniformValue; return; } if (!defined(material._textures[uniformId])) { material._texturePaths[uniformId] = undefined; material._textures[uniformId] = context.defaultCubeMap; } if (uniformValue === Material.DefaultCubeMapId) { return; } const path = uniformValue.positiveX + uniformValue.negativeX + uniformValue.positiveY + uniformValue.negativeY + uniformValue.positiveZ + uniformValue.negativeZ; if (path !== material._texturePaths[uniformId]) { const promises = [ Resource.createIfNeeded(uniformValue.positiveX).fetchImage(), Resource.createIfNeeded(uniformValue.negativeX).fetchImage(), Resource.createIfNeeded(uniformValue.positiveY).fetchImage(), Resource.createIfNeeded(uniformValue.negativeY).fetchImage(), Resource.createIfNeeded(uniformValue.positiveZ).fetchImage(), Resource.createIfNeeded(uniformValue.negativeZ).fetchImage(), ]; Promise.all(promises).then(function (images) { material._loadedCubeMaps.push({ id: uniformId, images: images, }); }); material._texturePaths[uniformId] = path; } }; } function createUniforms(material) { const uniforms = material._template.uniforms; for (const uniformId in uniforms) { if (uniforms.hasOwnProperty(uniformId)) { createUniform(material, uniformId); } } } // Writes uniform declarations to the shader file and connects uniform values with // corresponding material properties through the returnUniforms function. function createUniform(material, uniformId) { const strict = material._strict; const materialUniforms = material._template.uniforms; const uniformValue = materialUniforms[uniformId]; const uniformType = getUniformType(uniformValue); //>>includeStart('debug', pragmas.debug); if (!defined(uniformType)) { throw new DeveloperError( `fabric: uniform '${uniformId}' has invalid type.` ); } //>>includeEnd('debug'); let replacedTokenCount; if (uniformType === "channels") { replacedTokenCount = replaceToken(material, uniformId, uniformValue, false); //>>includeStart('debug', pragmas.debug); if (replacedTokenCount === 0 && strict) { throw new DeveloperError( `strict: shader source does not use channels '${uniformId}'.` ); } //>>includeEnd('debug'); } else { // Since webgl doesn't allow texture dimension queries in glsl, create a uniform to do it. // Check if the shader source actually uses texture dimensions before creating the uniform. if (uniformType === "sampler2D") { const imageDimensionsUniformName = `${uniformId}Dimensions`; if (getNumberOfTokens(material, imageDimensionsUniformName) > 0) { materialUniforms[imageDimensionsUniformName] = { type: "ivec3", x: 1, y: 1, }; createUniform(material, imageDimensionsUniformName); } } // Add uniform declaration to source code. const uniformDeclarationRegex = new RegExp( `uniform\\s+${uniformType}\\s+${uniformId}\\s*;` ); if (!uniformDeclarationRegex.test(material.shaderSource)) { const uniformDeclaration = `uniform ${uniformType} ${uniformId};`; material.shaderSource = uniformDeclaration + material.shaderSource; } const newUniformId = `${uniformId}_${material._count++}`; replacedTokenCount = replaceToken(material, uniformId, newUniformId); //>>includeStart('debug', pragmas.debug); if (replacedTokenCount === 1 && strict) { throw new DeveloperError( `strict: shader source does not use uniform '${uniformId}'.` ); } //>>includeEnd('debug'); // Set uniform value material.uniforms[uniformId] = uniformValue; if (uniformType === "sampler2D") { material._uniforms[newUniformId] = function () { return material._textures[uniformId]; }; material._updateFunctions.push(createTexture2DUpdateFunction(uniformId)); } else if (uniformType === "samplerCube") { material._uniforms[newUniformId] = function () { return material._textures[uniformId]; }; material._updateFunctions.push(createCubeMapUpdateFunction(uniformId)); } else if (uniformType.indexOf("mat") !== -1) { const scratchMatrix = new matrixMap[uniformType](); material._uniforms[newUniformId] = function () { return matrixMap[uniformType].fromColumnMajorArray( material.uniforms[uniformId], scratchMatrix ); }; } else { material._uniforms[newUniformId] = function () { return material.uniforms[uniformId]; }; } } } // Determines the uniform type based on the uniform in the template. function getUniformType(uniformValue) { let uniformType = uniformValue.type; if (!defined(uniformType)) { const type = typeof uniformValue; if (type === "number") { uniformType = "float"; } else if (type === "boolean") { uniformType = "bool"; } else if ( type === "string" || uniformValue instanceof Resource || uniformValue instanceof HTMLCanvasElement || uniformValue instanceof HTMLImageElement ) { if (/^([rgba]){1,4}$/i.test(uniformValue)) { uniformType = "channels"; } else if (uniformValue === Material.DefaultCubeMapId) { uniformType = "samplerCube"; } else { uniformType = "sampler2D"; } } else if (type === "object") { if (Array.isArray(uniformValue)) { if ( uniformValue.length === 4 || uniformValue.length === 9 || uniformValue.length === 16 ) { uniformType = `mat${Math.sqrt(uniformValue.length)}`; } } else { let numAttributes = 0; for (const attribute in uniformValue) { if (uniformValue.hasOwnProperty(attribute)) { numAttributes += 1; } } if (numAttributes >= 2 && numAttributes <= 4) { uniformType = `vec${numAttributes}`; } else if (numAttributes === 6) { uniformType = "samplerCube"; } } } } return uniformType; } // Create all sub-materials by combining source and uniforms together. function createSubMaterials(material) { const strict = material._strict; const subMaterialTemplates = material._template.materials; for (const subMaterialId in subMaterialTemplates) { if (subMaterialTemplates.hasOwnProperty(subMaterialId)) { // Construct the sub-material. const subMaterial = new Material({ strict: strict, fabric: subMaterialTemplates[subMaterialId], count: material._count, }); material._count = subMaterial._count; material._uniforms = combine( material._uniforms, subMaterial._uniforms, true ); material.materials[subMaterialId] = subMaterial; material._translucentFunctions = material._translucentFunctions.concat( subMaterial._translucentFunctions ); // Make the material's czm_getMaterial unique by appending the sub-material type. const originalMethodName = "czm_getMaterial"; const newMethodName = `${originalMethodName}_${material._count++}`; replaceToken(subMaterial, originalMethodName, newMethodName); material.shaderSource = subMaterial.shaderSource + material.shaderSource; // Replace each material id with an czm_getMaterial method call. const materialMethodCall = `${newMethodName}(materialInput)`; const tokensReplacedCount = replaceToken( material, subMaterialId, materialMethodCall ); //>>includeStart('debug', pragmas.debug); if (tokensReplacedCount === 0 && strict) { throw new DeveloperError( `strict: shader source does not use material '${subMaterialId}'.` ); } //>>includeEnd('debug'); } } } // Used for searching or replacing a token in a material's shader source with something else. // If excludePeriod is true, do not accept tokens that are preceded by periods. // http://stackoverflow.com/questions/641407/javascript-negative-lookbehind-equivalent function replaceToken(material, token, newToken, excludePeriod) { excludePeriod = defaultValue(excludePeriod, true); let count = 0; const suffixChars = "([\\w])?"; const prefixChars = `([\\w${excludePeriod ? "." : ""}])?`; const regExp = new RegExp(prefixChars + token + suffixChars, "g"); material.shaderSource = material.shaderSource.replace(regExp, function ( $0, $1, $2 ) { if ($1 || $2) { return $0; } count += 1; return newToken; }); return count; } function getNumberOfTokens(material, token, excludePeriod) { return replaceToken(material, token, token, excludePeriod); } Material._materialCache = { _materials: {}, addMaterial: function (type, materialTemplate) { this._materials[type] = materialTemplate; }, getMaterial: function (type) { return this._materials[type]; }, }; /** * Gets or sets the default texture uniform value. * @type {String} */ Material.DefaultImageId = "czm_defaultImage"; /** * Gets or sets the default cube map texture uniform value. * @type {String} */ Material.DefaultCubeMapId = "czm_defaultCubeMap"; /** * Gets the name of the color material. * @type {String} * @readonly */ Material.ColorType = "Color"; Material._materialCache.addMaterial(Material.ColorType, { fabric: { type: Material.ColorType, uniforms: { color: new Color(1.0, 0.0, 0.0, 0.5), }, components: { diffuse: "color.rgb", alpha: "color.a", }, }, translucent: function (material) { return material.uniforms.color.alpha < 1.0; }, }); /** * Gets the name of the image material. * @type {String} * @readonly */ Material.ImageType = "Image"; Material._materialCache.addMaterial(Material.ImageType, { fabric: { type: Material.ImageType, uniforms: { image: Material.DefaultImageId, repeat: new Cartesian2(1.0, 1.0), color: new Color(1.0, 1.0, 1.0, 1.0), }, components: { diffuse: "texture2D(image, fract(repeat * materialInput.st)).rgb * color.rgb", alpha: "texture2D(image, fract(repeat * materialInput.st)).a * color.a", }, }, translucent: function (material) { return material.uniforms.color.alpha < 1.0; }, }); /** * Gets the name of the diffuce map material. * @type {String} * @readonly */ Material.DiffuseMapType = "DiffuseMap"; Material._materialCache.addMaterial(Material.DiffuseMapType, { fabric: { type: Material.DiffuseMapType, uniforms: { image: Material.DefaultImageId, channels: "rgb", repeat: new Cartesian2(1.0, 1.0), }, components: { diffuse: "texture2D(image, fract(repeat * materialInput.st)).channels", }, }, translucent: false, }); /** * Gets the name of the alpha map material. * @type {String} * @readonly */ Material.AlphaMapType = "AlphaMap"; Material._materialCache.addMaterial(Material.AlphaMapType, { fabric: { type: Material.AlphaMapType, uniforms: { image: Material.DefaultImageId, channel: "a", repeat: new Cartesian2(1.0, 1.0), }, components: { alpha: "texture2D(image, fract(repeat * materialInput.st)).channel", }, }, translucent: true, }); /** * Gets the name of the specular map material. * @type {String} * @readonly */ Material.SpecularMapType = "SpecularMap"; Material._materialCache.addMaterial(Material.SpecularMapType, { fabric: { type: Material.SpecularMapType, uniforms: { image: Material.DefaultImageId, channel: "r", repeat: new Cartesian2(1.0, 1.0), }, components: { specular: "texture2D(image, fract(repeat * materialInput.st)).channel", }, }, translucent: false, }); /** * Gets the name of the emmision map material. * @type {String} * @readonly */ Material.EmissionMapType = "EmissionMap"; Material._materialCache.addMaterial(Material.EmissionMapType, { fabric: { type: Material.EmissionMapType, uniforms: { image: Material.DefaultImageId, channels: "rgb", repeat: new Cartesian2(1.0, 1.0), }, components: { emission: "texture2D(image, fract(repeat * materialInput.st)).channels", }, }, translucent: false, }); /** * Gets the name of the bump map material. * @type {String} * @readonly */ Material.BumpMapType = "BumpMap"; Material._materialCache.addMaterial(Material.BumpMapType, { fabric: { type: Material.BumpMapType, uniforms: { image: Material.DefaultImageId, channel: "r", strength: 0.8, repeat: new Cartesian2(1.0, 1.0), }, source: BumpMapMaterial, }, translucent: false, }); /** * Gets the name of the normal map material. * @type {String} * @readonly */ Material.NormalMapType = "NormalMap"; Material._materialCache.addMaterial(Material.NormalMapType, { fabric: { type: Material.NormalMapType, uniforms: { image: Material.DefaultImageId, channels: "rgb", strength: 0.8, repeat: new Cartesian2(1.0, 1.0), }, source: NormalMapMaterial, }, translucent: false, }); /** * Gets the name of the grid material. * @type {String} * @readonly */ Material.GridType = "Grid"; Material._materialCache.addMaterial(Material.GridType, { fabric: { type: Material.GridType, uniforms: { color: new Color(0.0, 1.0, 0.0, 1.0), cellAlpha: 0.1, lineCount: new Cartesian2(8.0, 8.0), lineThickness: new Cartesian2(1.0, 1.0), lineOffset: new Cartesian2(0.0, 0.0), }, source: GridMaterial, }, translucent: function (material) { const uniforms = material.uniforms; return uniforms.color.alpha < 1.0 || uniforms.cellAlpha < 1.0; }, }); /** * Gets the name of the stripe material. * @type {String} * @readonly */ Material.StripeType = "Stripe"; Material._materialCache.addMaterial(Material.StripeType, { fabric: { type: Material.StripeType, uniforms: { horizontal: true, evenColor: new Color(1.0, 1.0, 1.0, 0.5), oddColor: new Color(0.0, 0.0, 1.0, 0.5), offset: 0.0, repeat: 5.0, }, source: StripeMaterial, }, translucent: function (material) { const uniforms = material.uniforms; return uniforms.evenColor.alpha < 1.0 || uniforms.oddColor.alpha < 1.0; }, }); /** * Gets the name of the checkerboard material. * @type {String} * @readonly */ Material.CheckerboardType = "Checkerboard"; Material._materialCache.addMaterial(Material.CheckerboardType, { fabric: { type: Material.CheckerboardType, uniforms: { lightColor: new Color(1.0, 1.0, 1.0, 0.5), darkColor: new Color(0.0, 0.0, 0.0, 0.5), repeat: new Cartesian2(5.0, 5.0), }, source: CheckerboardMaterial, }, translucent: function (material) { const uniforms = material.uniforms; return uniforms.lightColor.alpha < 1.0 || uniforms.darkColor.alpha < 1.0; }, }); /** * Gets the name of the dot material. * @type {String} * @readonly */ Material.DotType = "Dot"; Material._materialCache.addMaterial(Material.DotType, { fabric: { type: Material.DotType, uniforms: { lightColor: new Color(1.0, 1.0, 0.0, 0.75), darkColor: new Color(0.0, 1.0, 1.0, 0.75), repeat: new Cartesian2(5.0, 5.0), }, source: DotMaterial, }, translucent: function (material) { const uniforms = material.uniforms; return uniforms.lightColor.alpha < 1.0 || uniforms.darkColor.alpha < 1.0; }, }); /** * Gets the name of the water material. * @type {String} * @readonly */ Material.WaterType = "Water"; Material._materialCache.addMaterial(Material.WaterType, { fabric: { type: Material.WaterType, uniforms: { baseWaterColor: new Color(0.2, 0.3, 0.6, 1.0), blendColor: new Color(0.0, 1.0, 0.699, 1.0), specularMap: Material.DefaultImageId, normalMap: Material.DefaultImageId, frequency: 10.0, animationSpeed: 0.01, amplitude: 1.0, specularIntensity: 0.5, fadeFactor: 1.0, }, source: WaterMaterial, }, translucent: function (material) { const uniforms = material.uniforms; return ( uniforms.baseWaterColor.alpha < 1.0 || uniforms.blendColor.alpha < 1.0 ); }, }); /** * Gets the name of the rim lighting material. * @type {String} * @readonly */ Material.RimLightingType = "RimLighting"; Material._materialCache.addMaterial(Material.RimLightingType, { fabric: { type: Material.RimLightingType, uniforms: { color: new Color(1.0, 0.0, 0.0, 0.7), rimColor: new Color(1.0, 1.0, 1.0, 0.4), width: 0.3, }, source: RimLightingMaterial, }, translucent: function (material) { const uniforms = material.uniforms; return uniforms.color.alpha < 1.0 || uniforms.rimColor.alpha < 1.0; }, }); /** * Gets the name of the fade material. * @type {String} * @readonly */ Material.FadeType = "Fade"; Material._materialCache.addMaterial(Material.FadeType, { fabric: { type: Material.FadeType, uniforms: { fadeInColor: new Color(1.0, 0.0, 0.0, 1.0), fadeOutColor: new Color(0.0, 0.0, 0.0, 0.0), maximumDistance: 0.5, repeat: true, fadeDirection: { x: true, y: true, }, time: new Cartesian2(0.5, 0.5), }, source: FadeMaterial, }, translucent: function (material) { const uniforms = material.uniforms; return ( uniforms.fadeInColor.alpha < 1.0 || uniforms.fadeOutColor.alpha < 1.0 ); }, }); /** * Gets the name of the polyline arrow material. * @type {String} * @readonly */ Material.PolylineArrowType = "PolylineArrow"; Material._materialCache.addMaterial(Material.PolylineArrowType, { fabric: { type: Material.PolylineArrowType, uniforms: { color: new Color(1.0, 1.0, 1.0, 1.0), }, source: PolylineArrowMaterial, }, translucent: true, }); /** * Gets the name of the polyline glow material. * @type {String} * @readonly */ Material.PolylineDashType = "PolylineDash"; Material._materialCache.addMaterial(Material.PolylineDashType, { fabric: { type: Material.PolylineDashType, uniforms: { color: new Color(1.0, 0.0, 1.0, 1.0), gapColor: new Color(0.0, 0.0, 0.0, 0.0), dashLength: 16.0, dashPattern: 255.0, }, source: PolylineDashMaterial, }, translucent: true, }); /** * Gets the name of the polyline glow material. * @type {String} * @readonly */ Material.PolylineGlowType = "PolylineGlow"; Material._materialCache.addMaterial(Material.PolylineGlowType, { fabric: { type: Material.PolylineGlowType, uniforms: { color: new Color(0.0, 0.5, 1.0, 1.0), glowPower: 0.25, taperPower: 1.0, }, source: PolylineGlowMaterial, }, translucent: true, }); /** * Gets the name of the polyline outline material. * @type {String} * @readonly */ Material.PolylineOutlineType = "PolylineOutline"; Material._materialCache.addMaterial(Material.PolylineOutlineType, { fabric: { type: Material.PolylineOutlineType, uniforms: { color: new Color(1.0, 1.0, 1.0, 1.0), outlineColor: new Color(1.0, 0.0, 0.0, 1.0), outlineWidth: 1.0, }, source: PolylineOutlineMaterial, }, translucent: function (material) { const uniforms = material.uniforms; return uniforms.color.alpha < 1.0 || uniforms.outlineColor.alpha < 1.0; }, }); /** * Gets the name of the elevation contour material. * @type {String} * @readonly */ Material.ElevationContourType = "ElevationContour"; Material._materialCache.addMaterial(Material.ElevationContourType, { fabric: { type: Material.ElevationContourType, uniforms: { spacing: 100.0, color: new Color(1.0, 0.0, 0.0, 1.0), width: 1.0, }, source: ElevationContourMaterial, }, translucent: false, }); /** * Gets the name of the elevation contour material. * @type {String} * @readonly */ Material.ElevationRampType = "ElevationRamp"; Material._materialCache.addMaterial(Material.ElevationRampType, { fabric: { type: Material.ElevationRampType, uniforms: { image: Material.DefaultImageId, minimumHeight: 0.0, maximumHeight: 10000.0, }, source: ElevationRampMaterial, }, translucent: false, }); /** * Gets the name of the slope ramp material. * @type {String} * @readonly */ Material.SlopeRampMaterialType = "SlopeRamp"; Material._materialCache.addMaterial(Material.SlopeRampMaterialType, { fabric: { type: Material.SlopeRampMaterialType, uniforms: { image: Material.DefaultImageId, }, source: SlopeRampMaterial, }, translucent: false, }); /** * Gets the name of the aspect ramp material. * @type {String} * @readonly */ Material.AspectRampMaterialType = "AspectRamp"; Material._materialCache.addMaterial(Material.AspectRampMaterialType, { fabric: { type: Material.AspectRampMaterialType, uniforms: { image: Material.DefaultImageId, }, source: AspectRampMaterial, }, translucent: false, }); /** * Gets the name of the elevation band material. * @type {String} * @readonly */ Material.ElevationBandType = "ElevationBand"; Material._materialCache.addMaterial(Material.ElevationBandType, { fabric: { type: Material.ElevationBandType, uniforms: { heights: Material.DefaultImageId, colors: Material.DefaultImageId, }, source: ElevationBandMaterial, }, translucent: true, }); export default Material;