import { Utils } from '../common/Utils';
import { CubeTexture } from '../resources/CubeTexture';
import { ImageFilterType, ImageWrapType, ImageMappingType } from '../const';
/**
* @public
* @typedef LoadTextureResourceSamplerInfo 纹理采样参数
* @property {ImageFilterType} minFilter 放大时的过滤方式
* @property {ImageFilterType} magFilter 缩小时的过滤方式
* @property {ImageWrapType} wrapS 水平方向的循环方式
* @property {ImageWrapType} wrapT 垂直方向的循环方式
* @property {ImageMappingType} mappingType 纹理映射类型 例如普通的UV映射 立方体映射 单张图作为环境图的映射
*/
/**
* When create texture.
* @callback onCreateTextureCallback
* @param {string} url The url.
* @param {LoadTextureResourceSamplerInfo} sampler The sampler info.
* @returns {THING.BaseTexture}
*/
/**
* When create texture in async mode.
* @callback onCreateTextureAsyncCallback
* @param {string} url The url.
* @param {LoadTextureResourceSamplerInfo} sampler The sampler info.
* @returns {Promise<any>}
*/
const __ = {
private: Symbol('private'),
}
const _defaultSampler = {
minFilter: ImageFilterType.LinearMipmapLinearFilter,
magFilter: ImageFilterType.LinearFilter,
wrapS: ImageWrapType.Repeat,
wrapT: ImageWrapType.Repeat,
mappingType: ImageMappingType.UV
};
const _defaultCubeSampler = {
minFilter: ImageFilterType.LinearMipmapLinearFilter,
magFilter: ImageFilterType.LinearFilter,
wrapS: ImageWrapType.ClampToEdge,
wrapT: ImageWrapType.ClampToEdge,
mappingType: ImageMappingType.CubeReflection
}
const _defaultOptions = {};
// #region Private Functions
// Build sampler key.
function _buildSamplerKey(sampler, defaultSampler) {
if (!sampler) {
return '';
}
let minFilter = sampler.minFilter ? sampler.minFilter : defaultSampler.minFilter;
let magFilter = sampler.magFilter ? sampler.magFilter : defaultSampler.magFilter;
let wrapS = sampler.wrapS ? sampler.wrapS : defaultSampler.wrapS;
let wrapT = sampler.wrapT ? sampler.wrapT : defaultSampler.wrapT;
let mappingType = sampler.mappingType ? sampler.mappingType : defaultSampler.mappingType;
return `${minFilter};${magFilter};${wrapS};${wrapT};${mappingType}`;
}
// Build url key.
function _buildUrlKey(url, flipY) {
if (Utils.isString(url)) {
// gltf no url, so url is empty string
if (url.length) {
return url + ';' + flipY;
}
return '';
}
else if (Utils.isArray(url)) {
return url.join(';') + flipY;
}
else {
return '';
}
}
// Update cache map.
function _updateTexture(urlKey, samplerKey, texture, map) {
let urlMap = map.get(urlKey);
if (!urlMap) {
urlMap = new Map();
map.set(urlKey, urlMap);
}
urlMap.set(samplerKey, texture);
}
// Clear resources.
function _clearResources(resourcesMap) {
resourcesMap.forEach(resources => {
resources.forEach(resource => {
resource.release();
});
});
resourcesMap.clear();
}
// #endregion
/**
* @class BaseTextureManager
* The base texture manager.
* @memberof THING
*/
class BaseTextureManager {
/**
* 基础纹理管理器类,用于管理纹理的加载和缓存
* @private
*/
constructor() {
/**
* When create texture.
* @member {OnCreateTextureCallback} onCreateTexture
* @memberof THING.BaseTextureManager
* @instance
* @private
*/
/**
* When create texture in async mode.
* @member {onCreateTextureAsyncCallback} onCreateTextureAsync
* @memberof THING.BaseTextureManager
* @instance
* @private
*/
this[__.private] = {};
let _private = this[__.private];
_private.texturesMap = new Map();
_private.cubeTexturesMap = new Map();
}
// #region Private
_updateTexture(url, samplerKey, texture) {
let _private = this[__.private];
let textures = new Map();
textures.set(samplerKey, texture);
_private.texturesMap.set(url, textures);
}
_loadTexture(urlKey, samplerKey, texture) {
const _private = this[__.private];
this._hookTexture(texture);
_updateTexture(urlKey, samplerKey, texture, _private.texturesMap);
}
_createTexture(url, urlKey, sampler, samplerKey, options) {
// Create texture
if (this.onCreateTexture) {
let texture = this.onCreateTexture(url, sampler, options);
if (texture) {
this._loadTexture(urlKey, samplerKey, texture);
}
return texture;
}
// Create texture in async mode
else if (this.onCreateTextureAsync) {
return this.onCreateTextureAsync(url, sampler, options).then((texture) => {
if (texture) {
this._loadTexture(urlKey, samplerKey, texture);
}
return texture;
});
}
// Do not how to create texture, we should impl create texture interface at least
else {
return null;
}
}
_createCubeTexture(param, urlKey, sampler, samplerKey, options) {
const _private = this[__.private];
const cubeTexture = new CubeTexture({
url: param.url,
flipY: Utils.parseValue(options['flipY'], false),
wrapTypeS: sampler.wrapS,
wrapTypeT: sampler.wrapT,
minFilterType: sampler.minFilter,
magFilterType: sampler.magFilter,
mappingType: sampler.mappingType
})
this._hookTexture(cubeTexture)
_updateTexture(urlKey, samplerKey, cubeTexture, _private.cubeTexturesMap)
return cubeTexture;
}
_getTexture(urlKey, samplerKey, map) {
const texture = map.get(urlKey)?.get(samplerKey)
if (!texture) {
return null;
}
// Check whether it has been disposed
if (texture.disposed) {
map.get(urlKey).delete(samplerKey);
return null;
}
return texture;
}
_hookTexture(texture) {
let _private = this[__.private];
// Change map key when update sampler
texture.onUpdateSampler = (scope, sampler) => {
const map = scope.isCubeTexture ? _private.cubeTexturesMap : _private.texturesMap;
const urlKey = _buildUrlKey(scope.url);
// Delete the old texture cache
const urlMap = map.get(urlKey);
if (!urlMap) {
return;
}
for (let [samplerKey, texture] of urlMap) {
if (texture == scope) {
// Delete the old texture cache
urlMap.delete(samplerKey);
const defaultSampler = scope.isCubeTexture ? _defaultCubeSampler : _defaultSampler;
// Add texture with new sampler values into cache
const newSamplerKey = _buildSamplerKey(sampler, defaultSampler);
urlMap.set(newSamplerKey, scope);
return;
}
}
};
// Change map key when update url
texture.onUpdateUrl = (scope) => {
const map = scope.isCubeTexture ? _private.cubeTexturesMap : _private.texturesMap;
for (let [urlKey, urlMap] of map) {
for (let [samplerKey, texture] of urlMap) {
if (texture == scope) {
// Delete the old texture cache
urlMap.delete(samplerKey);
const urlKey = _buildUrlKey(scope.url);
if (urlKey) {
_updateTexture(urlKey, samplerKey, texture, map);
}
return;
}
}
}
};
}
// #endregion
/**
* Dispose.
* @private
*/
dispose() {
let _private = this[__.private];
_clearResources(_private.texturesMap);
_clearResources(_private.cubeTexturesMap);
}
/**
* Load texture from URL.
* @param {string} url The resource path.
* @param {LoadTextureResourceSamplerInfo} sampler The sampler info.
* @param {object} options The options.
* @returns {THING.BaseTexture|Promise<any>}
* @private
*/
load(url, sampler = _defaultSampler, options = _defaultOptions) {
let _private = this[__.private];
// Build the url key
const urlKey = _buildUrlKey(url, Utils.parseValue(options['flipY'], true));
if (!urlKey) {
return null;
}
const samplerKey = _buildSamplerKey(sampler, _defaultSampler);
// Get cache map
const cacheMap = _private.texturesMap
let texture = this._getTexture(urlKey, samplerKey, cacheMap);
if (!texture) {
return this._createTexture(url, urlKey, sampler, samplerKey, options);
}
texture.addRef();
return texture;
}
/**
* Load cube texture from URL.
* @param {string} url The resource path.
* @param {LoadTextureResourceSamplerInfo} sampler The sampler info.
* @param {object} options The options.
* @returns {THING.CubeTexture|Promise<any>}
* @private
*/
loadCubeTexture(param, sampler = _defaultCubeSampler, options = _defaultOptions) {
const _private = this[__.private];
param = Utils.parseCubeTextureParams(param);
// Build the url key
const urlKey = _buildUrlKey(param.url, Utils.parseValue(options['flipY'], false));
if (!urlKey) {
return null;
}
const samplerKey = _buildSamplerKey(sampler, _defaultCubeSampler);
// Get cache texture
const cacheMap = _private.cubeTexturesMap
const cubeTexture = this._getTexture(urlKey, samplerKey, cacheMap);
if (!cubeTexture) {
return this._createCubeTexture(param, urlKey, sampler, samplerKey, options);
}
cubeTexture.addRef();
return cubeTexture;
}
/**
* Load texture from URL in async mode.
* @param {string} url The resource path.
* @param {LoadTextureResourceSamplerInfo} sampler The sampler info.
* @param {object} options The options.
* @returns {Promise<any>}
* @private
*/
loadAsync(url, sampler = _defaultSampler, options = _defaultOptions) {
let texture = this.load(url, sampler, options);
if (texture) {
if (texture.isBaseTexture) {
return texture.waitForComplete();
}
else {
return texture.then((result) => {
return result.waitForComplete();
});
}
}
else {
return Promise.reject(`Load '${url}' texture failed`);
}
}
getTextures(){
let _private = this[__.private];
return _private.texturesMap;
}
}
export { BaseTextureManager }