engine.cubeTexture.ts 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347
  1. import { ThinEngine } from "../../Engines/thinEngine";
  2. import { InternalTexture, InternalTextureSource } from '../../Materials/Textures/internalTexture';
  3. import { Logger } from '../../Misc/logger';
  4. import { Nullable } from '../../types';
  5. import { Scene } from '../../scene';
  6. import { IInternalTextureLoader } from '../../Materials/Textures/internalTextureLoader';
  7. import { FileTools } from '../../Misc/fileTools';
  8. import { DepthTextureCreationOptions } from '../depthTextureCreationOptions';
  9. import { IWebRequest } from '../../Misc/interfaces/iWebRequest';
  10. declare module "../../Engines/thinEngine" {
  11. export interface ThinEngine {
  12. /**
  13. * Creates a depth stencil cube texture.
  14. * This is only available in WebGL 2.
  15. * @param size The size of face edge in the cube texture.
  16. * @param options The options defining the cube texture.
  17. * @returns The cube texture
  18. */
  19. _createDepthStencilCubeTexture(size: number, options: DepthTextureCreationOptions): InternalTexture;
  20. /**
  21. * Creates a cube texture
  22. * @param rootUrl defines the url where the files to load is located
  23. * @param scene defines the current scene
  24. * @param files defines the list of files to load (1 per face)
  25. * @param noMipmap defines a boolean indicating that no mipmaps shall be generated (false by default)
  26. * @param onLoad defines an optional callback raised when the texture is loaded
  27. * @param onError defines an optional callback raised if there is an issue to load the texture
  28. * @param format defines the format of the data
  29. * @param forcedExtension defines the extension to use to pick the right loader
  30. * @param createPolynomials if a polynomial sphere should be created for the cube texture
  31. * @param lodScale defines the scale applied to environment texture. This manages the range of LOD level used for IBL according to the roughness
  32. * @param lodOffset defines the offset applied to environment texture. This manages first LOD level used for IBL according to the roughness
  33. * @param fallback defines texture to use while falling back when (compressed) texture file not found.
  34. * @param excludeLoaders array of texture loaders that should be excluded when picking a loader for the texture (defualt: empty array)
  35. * @returns the cube texture as an InternalTexture
  36. */
  37. createCubeTexture(rootUrl: string, scene: Nullable<Scene>, files: Nullable<string[]>, noMipmap: boolean | undefined,
  38. onLoad: Nullable<(data?: any) => void>, onError: Nullable<(message?: string, exception?: any) => void>,
  39. format: number | undefined, forcedExtension: any, createPolynomials: boolean, lodScale: number, lodOffset: number, fallback: Nullable<InternalTexture>, excludeLoaders: Array<IInternalTextureLoader>): InternalTexture;
  40. /**
  41. * Creates a cube texture
  42. * @param rootUrl defines the url where the files to load is located
  43. * @param scene defines the current scene
  44. * @param files defines the list of files to load (1 per face)
  45. * @param noMipmap defines a boolean indicating that no mipmaps shall be generated (false by default)
  46. * @param onLoad defines an optional callback raised when the texture is loaded
  47. * @param onError defines an optional callback raised if there is an issue to load the texture
  48. * @param format defines the format of the data
  49. * @param forcedExtension defines the extension to use to pick the right loader
  50. * @returns the cube texture as an InternalTexture
  51. */
  52. createCubeTexture(rootUrl: string, scene: Nullable<Scene>, files: Nullable<string[]>, noMipmap: boolean,
  53. onLoad: Nullable<(data?: any) => void>, onError: Nullable<(message?: string, exception?: any) => void>,
  54. format: number | undefined, forcedExtension: any): InternalTexture;
  55. /**
  56. * Creates a cube texture
  57. * @param rootUrl defines the url where the files to load is located
  58. * @param scene defines the current scene
  59. * @param files defines the list of files to load (1 per face)
  60. * @param noMipmap defines a boolean indicating that no mipmaps shall be generated (false by default)
  61. * @param onLoad defines an optional callback raised when the texture is loaded
  62. * @param onError defines an optional callback raised if there is an issue to load the texture
  63. * @param format defines the format of the data
  64. * @param forcedExtension defines the extension to use to pick the right loader
  65. * @param createPolynomials if a polynomial sphere should be created for the cube texture
  66. * @param lodScale defines the scale applied to environment texture. This manages the range of LOD level used for IBL according to the roughness
  67. * @param lodOffset defines the offset applied to environment texture. This manages first LOD level used for IBL according to the roughness
  68. * @returns the cube texture as an InternalTexture
  69. */
  70. createCubeTexture(rootUrl: string, scene: Nullable<Scene>, files: Nullable<string[]>, noMipmap: boolean,
  71. onLoad: Nullable<(data?: any) => void>, onError: Nullable<(message?: string, exception?: any) => void>,
  72. format: number | undefined, forcedExtension: any, createPolynomials: boolean, lodScale: number, lodOffset: number): InternalTexture;
  73. /** @hidden */
  74. _partialLoadFile(url: string, index: number, loadedFiles: (string | ArrayBuffer)[], onfinish: (files: (string | ArrayBuffer)[]) => void, onErrorCallBack: Nullable<(message?: string, exception?: any) => void>): void;
  75. /** @hidden */
  76. _cascadeLoadFiles(scene: Nullable<Scene>, onfinish: (images: (string | ArrayBuffer)[]) => void, files: string[], onError: Nullable<(message?: string, exception?: any) => void>): void;
  77. /** @hidden */
  78. _cascadeLoadImgs(scene: Nullable<Scene>, onfinish: (images: HTMLImageElement[]) => void, files: string[], onError: Nullable<(message?: string, exception?: any) => void>, mimeType?: string): void;
  79. /** @hidden */
  80. _partialLoadImg(url: string, index: number, loadedImages: HTMLImageElement[], scene: Nullable<Scene>, onfinish: (images: HTMLImageElement[]) => void, onErrorCallBack: Nullable<(message?: string, exception?: any) => void>, mimeType?: string): void;
  81. /**
  82. * @hidden
  83. */
  84. _setCubeMapTextureParams(loadMipmap: boolean): void;
  85. }
  86. }
  87. ThinEngine.prototype._createDepthStencilCubeTexture = function(size: number, options: DepthTextureCreationOptions): InternalTexture {
  88. var internalTexture = new InternalTexture(this, InternalTextureSource.Unknown);
  89. internalTexture.isCube = true;
  90. if (this.webGLVersion === 1) {
  91. Logger.Error("Depth cube texture is not supported by WebGL 1.");
  92. return internalTexture;
  93. }
  94. var internalOptions = {
  95. bilinearFiltering: false,
  96. comparisonFunction: 0,
  97. generateStencil: false,
  98. ...options
  99. };
  100. var gl = this._gl;
  101. this._bindTextureDirectly(gl.TEXTURE_CUBE_MAP, internalTexture, true);
  102. this._setupDepthStencilTexture(internalTexture, size, internalOptions.generateStencil, internalOptions.bilinearFiltering, internalOptions.comparisonFunction);
  103. // Create the depth/stencil buffer
  104. for (var face = 0; face < 6; face++) {
  105. if (internalOptions.generateStencil) {
  106. gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + face, 0, gl.DEPTH24_STENCIL8, size, size, 0, gl.DEPTH_STENCIL, gl.UNSIGNED_INT_24_8, null);
  107. }
  108. else {
  109. gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X + face, 0, gl.DEPTH_COMPONENT24, size, size, 0, gl.DEPTH_COMPONENT, gl.UNSIGNED_INT, null);
  110. }
  111. }
  112. this._bindTextureDirectly(gl.TEXTURE_CUBE_MAP, null);
  113. return internalTexture;
  114. };
  115. ThinEngine.prototype._partialLoadFile = function(url: string, index: number, loadedFiles: (string | ArrayBuffer)[],
  116. onfinish: (files: (string | ArrayBuffer)[]) => void, onErrorCallBack: Nullable<(message?: string, exception?: any) => void> = null): void {
  117. var onload = (data: string | ArrayBuffer) => {
  118. loadedFiles[index] = data;
  119. (<any>loadedFiles)._internalCount++;
  120. if ((<any>loadedFiles)._internalCount === 6) {
  121. onfinish(loadedFiles);
  122. }
  123. };
  124. const onerror = (request?: IWebRequest, exception?: any) => {
  125. if (onErrorCallBack && request) {
  126. onErrorCallBack(request.status + " " + request.statusText, exception);
  127. }
  128. };
  129. this._loadFile(url, onload, undefined, undefined, true, onerror);
  130. };
  131. ThinEngine.prototype._cascadeLoadFiles = function(scene: Nullable<Scene>, onfinish: (images: (string | ArrayBuffer)[]) => void, files: string[], onError: Nullable<(message?: string, exception?: any) => void> = null): void {
  132. var loadedFiles: (string | ArrayBuffer)[] = [];
  133. (<any>loadedFiles)._internalCount = 0;
  134. for (let index = 0; index < 6; index++) {
  135. this._partialLoadFile(files[index], index, loadedFiles, onfinish, onError);
  136. }
  137. };
  138. ThinEngine.prototype._cascadeLoadImgs = function(scene: Nullable<Scene>,
  139. onfinish: (images: HTMLImageElement[]) => void, files: string[], onError: Nullable<(message?: string, exception?: any) => void> = null, mimeType?: string) {
  140. var loadedImages: HTMLImageElement[] = [];
  141. (<any>loadedImages)._internalCount = 0;
  142. for (let index = 0; index < 6; index++) {
  143. this._partialLoadImg(files[index], index, loadedImages, scene, onfinish, onError, mimeType);
  144. }
  145. };
  146. ThinEngine.prototype._partialLoadImg = function(url: string, index: number, loadedImages: HTMLImageElement[], scene: Nullable<Scene>,
  147. onfinish: (images: HTMLImageElement[]) => void, onErrorCallBack: Nullable<(message?: string, exception?: any) => void> = null, mimeType?: string) {
  148. var img: Nullable<HTMLImageElement>;
  149. var onload = () => {
  150. if (img) {
  151. loadedImages[index] = img;
  152. (<any>loadedImages)._internalCount++;
  153. if (scene) {
  154. scene._removePendingData(img);
  155. }
  156. }
  157. if ((<any>loadedImages)._internalCount === 6) {
  158. onfinish(loadedImages);
  159. }
  160. };
  161. var onerror = (message?: string, exception?: any) => {
  162. if (scene) {
  163. scene._removePendingData(img);
  164. }
  165. if (onErrorCallBack) {
  166. onErrorCallBack(message, exception);
  167. }
  168. };
  169. img = FileTools.LoadImage(url, onload, onerror, scene ? scene.offlineProvider : null, mimeType);
  170. if (scene && img) {
  171. scene._addPendingData(img);
  172. }
  173. };
  174. ThinEngine.prototype._setCubeMapTextureParams = function(loadMipmap: boolean): void {
  175. var gl = this._gl;
  176. gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
  177. gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, loadMipmap ? gl.LINEAR_MIPMAP_LINEAR : gl.LINEAR);
  178. gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
  179. gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
  180. this._bindTextureDirectly(gl.TEXTURE_CUBE_MAP, null);
  181. };
  182. ThinEngine.prototype.createCubeTexture = function(rootUrl: string, scene: Nullable<Scene>, files: Nullable<string[]>, noMipmap?: boolean, onLoad: Nullable<(data?: any) => void> = null, onError: Nullable<(message?: string, exception?: any) => void> = null, format?: number, forcedExtension: any = null, createPolynomials: boolean = false, lodScale: number = 0, lodOffset: number = 0, fallback: Nullable<InternalTexture> = null, excludeLoaders: Array<IInternalTextureLoader> = []): InternalTexture {
  183. var gl = this._gl;
  184. var texture = fallback ? fallback : new InternalTexture(this, InternalTextureSource.Cube);
  185. texture.isCube = true;
  186. texture.url = rootUrl;
  187. texture.generateMipMaps = !noMipmap;
  188. texture._lodGenerationScale = lodScale;
  189. texture._lodGenerationOffset = lodOffset;
  190. if (!this._doNotHandleContextLost) {
  191. texture._extension = forcedExtension;
  192. texture._files = files;
  193. }
  194. var lastDot = rootUrl.lastIndexOf('.');
  195. var extension = forcedExtension ? forcedExtension : (lastDot > -1 ? rootUrl.substring(lastDot).toLowerCase() : "");
  196. const filteredFormat: Nullable<string> = this.excludedCompressedTextureFormats(rootUrl, this._textureFormatInUse);
  197. let loader: Nullable<IInternalTextureLoader> = null;
  198. for (let availableLoader of ThinEngine._TextureLoaders) {
  199. if (excludeLoaders.indexOf(availableLoader) === -1 && availableLoader.canLoad(extension, filteredFormat, fallback, false, false)) {
  200. loader = availableLoader;
  201. break;
  202. }
  203. }
  204. let onInternalError = (request?: IWebRequest, exception?: any) => {
  205. if (loader) {
  206. const fallbackUrl = loader.getFallbackTextureUrl(texture.url, this._textureFormatInUse);
  207. Logger.Warn((loader.constructor as any).name + " failed when trying to load " + texture.url + ", falling back to the next supported loader");
  208. if (fallbackUrl) {
  209. excludeLoaders.push(loader);
  210. this.createCubeTexture(fallbackUrl, scene, files, noMipmap, onLoad, onError, format, extension, createPolynomials, lodScale, lodOffset, texture, excludeLoaders);
  211. return;
  212. }
  213. }
  214. if (onError && request) {
  215. onError(request.status + " " + request.statusText, exception);
  216. }
  217. };
  218. if (loader) {
  219. rootUrl = loader.transformUrl(rootUrl, filteredFormat);
  220. const onloaddata = (data: any) => {
  221. this._bindTextureDirectly(gl.TEXTURE_CUBE_MAP, texture, true);
  222. loader!.loadCubeData(data, texture, createPolynomials, onLoad, onError);
  223. };
  224. if (files && files.length === 6) {
  225. if (loader.supportCascades) {
  226. this._cascadeLoadFiles(scene, onloaddata, files, onError);
  227. }
  228. else {
  229. if (onError) {
  230. onError("Textures type does not support cascades.");
  231. } else {
  232. Logger.Warn("Texture loader does not support cascades.");
  233. }
  234. }
  235. }
  236. else {
  237. this._loadFile(rootUrl, onloaddata, undefined, undefined, true, onInternalError);
  238. }
  239. }
  240. else {
  241. if (!files) {
  242. throw new Error("Cannot load cubemap because files were not defined");
  243. }
  244. this._cascadeLoadImgs(scene, (imgs) => {
  245. var width = this.needPOTTextures ? ThinEngine.GetExponentOfTwo(imgs[0].width, this._caps.maxCubemapTextureSize) : imgs[0].width;
  246. var height = width;
  247. var faces = [
  248. gl.TEXTURE_CUBE_MAP_POSITIVE_X, gl.TEXTURE_CUBE_MAP_POSITIVE_Y, gl.TEXTURE_CUBE_MAP_POSITIVE_Z,
  249. gl.TEXTURE_CUBE_MAP_NEGATIVE_X, gl.TEXTURE_CUBE_MAP_NEGATIVE_Y, gl.TEXTURE_CUBE_MAP_NEGATIVE_Z
  250. ];
  251. this._bindTextureDirectly(gl.TEXTURE_CUBE_MAP, texture, true);
  252. this._unpackFlipY(false);
  253. let internalFormat = format ? this._getInternalFormat(format) : this._gl.RGBA;
  254. for (var index = 0; index < faces.length; index++) {
  255. if (imgs[index].width !== width || imgs[index].height !== height) {
  256. this._prepareWorkingCanvas();
  257. if (!this._workingCanvas || !this._workingContext) {
  258. Logger.Warn("Cannot create canvas to resize texture.");
  259. return;
  260. }
  261. this._workingCanvas.width = width;
  262. this._workingCanvas.height = height;
  263. this._workingContext.drawImage(imgs[index], 0, 0, imgs[index].width, imgs[index].height, 0, 0, width, height);
  264. gl.texImage2D(faces[index], 0, internalFormat, internalFormat, gl.UNSIGNED_BYTE, this._workingCanvas);
  265. } else {
  266. gl.texImage2D(faces[index], 0, internalFormat, internalFormat, gl.UNSIGNED_BYTE, imgs[index]);
  267. }
  268. }
  269. if (!noMipmap) {
  270. gl.generateMipmap(gl.TEXTURE_CUBE_MAP);
  271. }
  272. this._setCubeMapTextureParams(!noMipmap);
  273. texture.width = width;
  274. texture.height = height;
  275. texture.isReady = true;
  276. if (format) {
  277. texture.format = format;
  278. }
  279. texture.onLoadedObservable.notifyObservers(texture);
  280. texture.onLoadedObservable.clear();
  281. if (onLoad) {
  282. onLoad();
  283. }
  284. }, files, onError);
  285. }
  286. this._internalTexturesCache.push(texture);
  287. return texture;
  288. };