equiRectangularCubeTexture.ts 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. import { PanoramaToCubeMapTools } from '../../Misc/HighDynamicRange/panoramaToCubemap';
  2. import { BaseTexture } from './baseTexture';
  3. import { Texture } from './texture';
  4. import { Scene } from "../../scene";
  5. import { Nullable } from "../../types";
  6. import { Tools } from '../../Misc/tools';
  7. import "../../Engines/Extensions/engine.rawTexture";
  8. import { Constants } from '../../Engines/constants';
  9. /**
  10. * This represents a texture coming from an equirectangular image supported by the web browser canvas.
  11. */
  12. export class EquiRectangularCubeTexture extends BaseTexture {
  13. /** The six faces of the cube. */
  14. private static _FacesMapping = ['right', 'left', 'up', 'down', 'front', 'back'];
  15. private _noMipmap: boolean;
  16. private _onLoad: Nullable<() => void> = null;
  17. private _onError: Nullable<() => void> = null;
  18. /** The size of the cubemap. */
  19. private _size: number;
  20. /** The buffer of the image. */
  21. private _buffer: ArrayBuffer;
  22. /** The width of the input image. */
  23. private _width: number;
  24. /** The height of the input image. */
  25. private _height: number;
  26. /** The URL to the image. */
  27. public url: string;
  28. /** The texture coordinates mode. As this texture is stored in a cube format, please modify carefully. */
  29. public coordinatesMode = Texture.CUBIC_MODE;
  30. /**
  31. * Instantiates an EquiRectangularCubeTexture from the following parameters.
  32. * @param url The location of the image
  33. * @param scene The scene the texture will be used in
  34. * @param size The cubemap desired size (the more it increases the longer the generation will be)
  35. * @param noMipmap Forces to not generate the mipmap if true
  36. * @param gammaSpace Specifies if the texture will be used in gamma or linear space
  37. * (the PBR material requires those textures in linear space, but the standard material would require them in Gamma space)
  38. * @param onLoad — defines a callback called when texture is loaded
  39. * @param onError — defines a callback called if there is an error
  40. */
  41. constructor(
  42. url: string,
  43. scene: Scene,
  44. size: number,
  45. noMipmap: boolean = false,
  46. gammaSpace: boolean = true,
  47. onLoad: Nullable<() => void> = null,
  48. onError: Nullable<(message?: string, exception?: any) => void> = null
  49. ) {
  50. super(scene);
  51. if (!url) {
  52. throw new Error('Image url is not set');
  53. }
  54. this.name = url;
  55. this.url = url;
  56. this._size = size;
  57. this._noMipmap = noMipmap;
  58. this.gammaSpace = gammaSpace;
  59. this._onLoad = onLoad;
  60. this._onError = onError;
  61. this.hasAlpha = false;
  62. this.isCube = true;
  63. this._texture = this._getFromCache(url, this._noMipmap);
  64. if (!this._texture) {
  65. if (!scene.useDelayedTextureLoading) {
  66. this.loadImage(this.loadTexture.bind(this), this._onError);
  67. } else {
  68. this.delayLoadState = Constants.DELAYLOADSTATE_NOTLOADED;
  69. }
  70. } else if (onLoad) {
  71. if (this._texture.isReady) {
  72. Tools.SetImmediate(() => onLoad());
  73. } else {
  74. this._texture.onLoadedObservable.add(onLoad);
  75. }
  76. }
  77. }
  78. /**
  79. * Load the image data, by putting the image on a canvas and extracting its buffer.
  80. */
  81. private loadImage(loadTextureCallback: () => void, onError: Nullable<(message?: string, exception?: any) => void>): void {
  82. const canvas = document.createElement('canvas');
  83. const image = new Image();
  84. image.addEventListener('load', () => {
  85. this._width = image.width;
  86. this._height = image.height;
  87. canvas.width = this._width;
  88. canvas.height = this._height;
  89. const ctx = canvas.getContext('2d') as CanvasRenderingContext2D;
  90. ctx.drawImage(image, 0, 0);
  91. const imageData = ctx.getImageData(0, 0, image.width, image.height);
  92. this._buffer = imageData.data.buffer as ArrayBuffer;
  93. canvas.remove();
  94. loadTextureCallback();
  95. });
  96. image.addEventListener('error', (error) => {
  97. if (onError) {
  98. onError(`${this.getClassName()} could not be loaded`, error);
  99. }
  100. });
  101. image.src = this.url;
  102. }
  103. /**
  104. * Convert the image buffer into a cubemap and create a CubeTexture.
  105. */
  106. private loadTexture(): void {
  107. const scene = this.getScene();
  108. const callback = (): Nullable<ArrayBufferView[]> => {
  109. const imageData = this.getFloat32ArrayFromArrayBuffer(this._buffer);
  110. // Extract the raw linear data.
  111. const data = PanoramaToCubeMapTools.ConvertPanoramaToCubemap(imageData, this._width, this._height, this._size);
  112. const results = [];
  113. // Push each faces.
  114. for (let i = 0; i < 6; i++) {
  115. const dataFace = (data as any)[EquiRectangularCubeTexture._FacesMapping[i]];
  116. results.push(dataFace);
  117. }
  118. return results;
  119. };
  120. if (!scene) {
  121. return;
  122. }
  123. this._texture = scene
  124. .getEngine()
  125. .createRawCubeTextureFromUrl(
  126. this.url,
  127. scene,
  128. this._size,
  129. Constants.TEXTUREFORMAT_RGB,
  130. scene.getEngine().getCaps().textureFloat
  131. ? Constants.TEXTURETYPE_FLOAT
  132. : Constants.TEXTURETYPE_UNSIGNED_INTEGER,
  133. this._noMipmap,
  134. callback,
  135. null,
  136. this._onLoad,
  137. this._onError
  138. );
  139. }
  140. /**
  141. * Convert the ArrayBuffer into a Float32Array and drop the transparency channel.
  142. * @param buffer The ArrayBuffer that should be converted.
  143. * @returns The buffer as Float32Array.
  144. */
  145. private getFloat32ArrayFromArrayBuffer(buffer: ArrayBuffer): Float32Array {
  146. const dataView = new DataView(buffer);
  147. const floatImageData = new Float32Array((buffer.byteLength * 3) / 4);
  148. let k = 0;
  149. for (let i = 0; i < buffer.byteLength; i++) {
  150. // We drop the transparency channel, because we do not need/want it
  151. if ((i + 1) % 4 !== 0) {
  152. floatImageData[k++] = dataView.getUint8(i) / 255;
  153. }
  154. }
  155. return floatImageData;
  156. }
  157. /**
  158. * Get the current class name of the texture useful for serialization or dynamic coding.
  159. * @returns "EquiRectangularCubeTexture"
  160. */
  161. public getClassName(): string {
  162. return "EquiRectangularCubeTexture";
  163. }
  164. /**
  165. * Create a clone of the current EquiRectangularCubeTexture and return it.
  166. * @returns A clone of the current EquiRectangularCubeTexture.
  167. */
  168. public clone(): EquiRectangularCubeTexture {
  169. const scene = this.getScene();
  170. if (!scene) {
  171. return this;
  172. }
  173. const newTexture = new EquiRectangularCubeTexture(this.url, scene, this._size, this._noMipmap, this.gammaSpace);
  174. // Base texture
  175. newTexture.level = this.level;
  176. newTexture.wrapU = this.wrapU;
  177. newTexture.wrapV = this.wrapV;
  178. newTexture.coordinatesIndex = this.coordinatesIndex;
  179. newTexture.coordinatesMode = this.coordinatesMode;
  180. return newTexture;
  181. }
  182. }