import defined from "../Core/defined.js";
import destroyObject from "../Core/destroyObject.js";
import Event from "../Core/Event.js";
import loadKTX2 from "../Core/loadKTX2.js";
import PixelFormat from "../Core/PixelFormat.js";
import CubeMap from "../Renderer/CubeMap.js";
import PixelDatatype from "../Renderer/PixelDatatype.js";
import Sampler from "../Renderer/Sampler.js";
import TextureMinificationFilter from "../Renderer/TextureMinificationFilter.js";
/**
* Manages a cube map for use as a specular environment map.
*
* @alias SpecularEnvironmentCubeMap
* @constructor
*
* @param {string} url The url to the KTX2 file containing the specular environment map and convoluted mipmaps.
* @private
*/
function SpecularEnvironmentCubeMap(url) {
this._url = url;
this._cubeMapBuffers = undefined;
this._texture = undefined;
this._maximumMipmapLevel = undefined;
this._loading = false;
this._ready = false;
this._errorEvent = new Event();
}
Object.defineProperties(SpecularEnvironmentCubeMap.prototype, {
/**
* The url to the KTX2 file containing the specular environment map and convoluted mipmaps.
* @memberof SpecularEnvironmentCubeMap.prototype
* @type {string}
* @readonly
*/
url: {
get: function () {
return this._url;
},
},
/**
* Gets an event that is raised when encountering an asynchronous error. By subscribing
* to the event, you will be notified of the error and can potentially recover from it.
* @memberof SpecularEnvironmentCubeMap.prototype
* @type {Event}
* @readonly
*/
errorEvent: {
get: function () {
return this._errorEvent;
},
},
/**
* A texture containing all the packed convolutions.
* @memberof SpecularEnvironmentCubeMap.prototype
* @type {Texture}
* @readonly
*/
texture: {
get: function () {
return this._texture;
},
},
/**
* The maximum number of mip levels with valid environment map data.
* This may differ from the number of mips in the WebGL cubemap.
* The data loaded at maximumMipmapLevel is suitable for
* PBR rendering of a material with maximum roughness (1.0).
* @memberOf SpecularEnvironmentCubeMap.prototype
* @type {number}
* @readonly
*/
maximumMipmapLevel: {
get: function () {
return this._maximumMipmapLevel;
},
},
/**
* Determines if the cube map is complete and ready to use.
* @memberof SpecularEnvironmentCubeMap.prototype
* @type {boolean}
* @readonly
*/
ready: {
get: function () {
return this._ready;
},
},
});
SpecularEnvironmentCubeMap.isSupported = function (context) {
const supportsFloatBuffersAndTextures =
(context.colorBufferHalfFloat && context.halfFloatingPointTexture) ||
(context.floatingPointTexture && context.colorBufferFloat);
return supportsFloatBuffersAndTextures && context.supportsTextureLod;
};
function cleanupResources(map) {
map._cubeMapBuffers = undefined;
}
/**
* Loads the environment map image and constructs the cube map for specular radiance calculations.
*
* Once the image is loaded, the next call cleans up unused resources. Every call after that is a no-op. *
* @param {FrameState} frameState The frame state. * * @private */ SpecularEnvironmentCubeMap.prototype.update = function (frameState) { const { context } = frameState; if (!SpecularEnvironmentCubeMap.isSupported(context)) { return; } if (defined(this._texture)) { cleanupResources(this); return; } if (!defined(this._texture) && !this._loading) { const cachedTexture = context.textureCache.getTexture(this._url); if (defined(cachedTexture)) { cleanupResources(this); this._texture = cachedTexture; this._maximumMipmapLevel = this._texture.maximumMipmapLevel; this._ready = true; } } const cubeMapBuffers = this._cubeMapBuffers; if (!defined(cubeMapBuffers) && !this._loading) { const that = this; loadKTX2(this._url) .then(function (buffers) { that._cubeMapBuffers = buffers; that._loading = false; }) .catch(function (error) { if (that.isDestroyed()) { return; } that._errorEvent.raiseEvent(error); }); this._loading = true; } if (!defined(this._cubeMapBuffers)) { return; } // Datatype is defined if it is a normalized type (i.e. ..._UNORM, ..._SFLOAT) let { pixelDatatype } = cubeMapBuffers[0].positiveX; if (!defined(pixelDatatype)) { pixelDatatype = context.halfFloatingPointTexture ? PixelDatatype.HALF_FLOAT : PixelDatatype.FLOAT; } const pixelFormat = PixelFormat.RGBA; const mipLevels = cubeMapBuffers.length; this._maximumMipmapLevel = mipLevels - 1; const faceSize = cubeMapBuffers[0].positiveX.width; const expectedMipLevels = Math.log2(faceSize) + 1; if (mipLevels !== expectedMipLevels) { // We assume the last supplied mip level was computed as the specular radiance // for roughness 1.0. // Fill the remaining levels with null values, to avoid WebGL errors. const dummyMipLevel = {}; Object.values(CubeMap.FaceName).forEach((faceName) => { dummyMipLevel[faceName] = undefined; }); for (let mipLevel = mipLevels; mipLevel < expectedMipLevels; mipLevel++) { cubeMapBuffers.push(dummyMipLevel); } } const sampler = new Sampler({ minificationFilter: TextureMinificationFilter.LINEAR_MIPMAP_LINEAR, }); const cubeMap = new CubeMap({ context: context, source: cubeMapBuffers[0], flipY: false, pixelDatatype: pixelDatatype, pixelFormat: pixelFormat, sampler: sampler, }); cubeMap.loadMipmaps(cubeMapBuffers.slice(1)); this._texture = cubeMap; this._texture.maximumMipmapLevel = this._maximumMipmapLevel; context.textureCache.addTexture(this._url, this._texture); this._ready = true; }; /** * 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.
*
true if this object was destroyed; otherwise, false.
*
* @see SpecularEnvironmentCubeMap#destroy
*/
SpecularEnvironmentCubeMap.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.
*