engine.cubeTexture.ts 16 KB

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