cubeTexture.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  1. import { __decorate } from "../../tslib.es6.js";
  2. import { serialize, serializeAsMatrix, serializeAsVector3 } from "../../Misc/decorators.js";
  3. import { Tools } from "../../Misc/tools.js";
  4. import { Matrix, TmpVectors, Vector3 } from "../../Maths/math.vector.js";
  5. import { BaseTexture } from "../../Materials/Textures/baseTexture.js";
  6. import { Texture } from "../../Materials/Textures/texture.js";
  7. import { GetClass, RegisterClass } from "../../Misc/typeStore.js";
  8. import "../../Engines/AbstractEngine/abstractEngine.cubeTexture.js";
  9. import "../../Engines/Extensions/engine.cubeTexture.js";
  10. import { Observable } from "../../Misc/observable.js";
  11. import { SerializationHelper } from "../../Misc/decorators.serialization.js";
  12. /**
  13. * Class for creating a cube texture
  14. */
  15. export class CubeTexture extends BaseTexture {
  16. /**
  17. * Gets or sets the size of the bounding box associated with the cube texture
  18. * When defined, the cubemap will switch to local mode
  19. * @see https://community.arm.com/graphics/b/blog/posts/reflections-based-on-local-cubemaps-in-unity
  20. * @example https://www.babylonjs-playground.com/#RNASML
  21. */
  22. set boundingBoxSize(value) {
  23. if (this._boundingBoxSize && this._boundingBoxSize.equals(value)) {
  24. return;
  25. }
  26. this._boundingBoxSize = value;
  27. const scene = this.getScene();
  28. if (scene) {
  29. scene.markAllMaterialsAsDirty(1);
  30. }
  31. }
  32. /**
  33. * Returns the bounding box size
  34. * @see https://doc.babylonjs.com/features/featuresDeepDive/materials/using/reflectionTexture#using-local-cubemap-mode
  35. */
  36. get boundingBoxSize() {
  37. return this._boundingBoxSize;
  38. }
  39. /**
  40. * Sets texture matrix rotation angle around Y axis in radians.
  41. */
  42. set rotationY(value) {
  43. this._rotationY = value;
  44. this.setReflectionTextureMatrix(Matrix.RotationY(this._rotationY));
  45. }
  46. /**
  47. * Gets texture matrix rotation angle around Y axis radians.
  48. */
  49. get rotationY() {
  50. return this._rotationY;
  51. }
  52. /**
  53. * Are mip maps generated for this texture or not.
  54. */
  55. get noMipmap() {
  56. return this._noMipmap;
  57. }
  58. /**
  59. * Gets the forced extension (if any)
  60. */
  61. get forcedExtension() {
  62. return this._forcedExtension;
  63. }
  64. /**
  65. * Creates a cube texture from an array of image urls
  66. * @param files defines an array of image urls
  67. * @param scene defines the hosting scene
  68. * @param noMipmap specifies if mip maps are not used
  69. * @returns a cube texture
  70. */
  71. static CreateFromImages(files, scene, noMipmap) {
  72. let rootUrlKey = "";
  73. files.forEach((url) => (rootUrlKey += url));
  74. return new CubeTexture(rootUrlKey, scene, null, noMipmap, files);
  75. }
  76. /**
  77. * Creates and return a texture created from prefilterd data by tools like IBL Baker or Lys.
  78. * @param url defines the url of the prefiltered texture
  79. * @param scene defines the scene the texture is attached to
  80. * @param forcedExtension defines the extension of the file if different from the url
  81. * @param createPolynomials defines whether or not to create polynomial harmonics from the texture data if necessary
  82. * @returns the prefiltered texture
  83. */
  84. static CreateFromPrefilteredData(url, scene, forcedExtension = null, createPolynomials = true) {
  85. const oldValue = scene.useDelayedTextureLoading;
  86. scene.useDelayedTextureLoading = false;
  87. const result = new CubeTexture(url, scene, null, false, null, null, null, undefined, true, forcedExtension, createPolynomials);
  88. scene.useDelayedTextureLoading = oldValue;
  89. return result;
  90. }
  91. /**
  92. * Creates a cube texture to use with reflection for instance. It can be based upon dds or six images as well
  93. * as prefiltered data.
  94. * @param rootUrl defines the url of the texture or the root name of the six images
  95. * @param sceneOrEngine defines the scene or engine the texture is attached to
  96. * @param extensions defines the suffixes add to the picture name in case six images are in use like _px.jpg...
  97. * @param noMipmap defines if mipmaps should be created or not
  98. * @param files defines the six files to load for the different faces in that order: px, py, pz, nx, ny, nz
  99. * @param onLoad defines a callback triggered at the end of the file load if no errors occurred
  100. * @param onError defines a callback triggered in case of error during load
  101. * @param format defines the internal format to use for the texture once loaded
  102. * @param prefiltered defines whether or not the texture is created from prefiltered data
  103. * @param forcedExtension defines the extensions to use (force a special type of file to load) in case it is different from the file name
  104. * @param createPolynomials defines whether or not to create polynomial harmonics from the texture data if necessary
  105. * @param lodScale defines the scale applied to environment texture. This manages the range of LOD level used for IBL according to the roughness
  106. * @param lodOffset defines the offset applied to environment texture. This manages first LOD level used for IBL according to the roughness
  107. * @param loaderOptions options to be passed to the loader
  108. * @param useSRGBBuffer Defines if the texture must be loaded in a sRGB GPU buffer (if supported by the GPU) (default: false)
  109. * @returns the cube texture
  110. */
  111. constructor(rootUrl, sceneOrEngine, extensions = null, noMipmap = false, files = null, onLoad = null, onError = null, format = 5, prefiltered = false, forcedExtension = null, createPolynomials = false, lodScale = 0.8, lodOffset = 0, loaderOptions, useSRGBBuffer) {
  112. super(sceneOrEngine);
  113. this._lodScale = 0.8;
  114. this._lodOffset = 0;
  115. /**
  116. * Observable triggered once the texture has been loaded.
  117. */
  118. this.onLoadObservable = new Observable();
  119. /**
  120. * Gets or sets the center of the bounding box associated with the cube texture.
  121. * It must define where the camera used to render the texture was set
  122. * @see https://doc.babylonjs.com/features/featuresDeepDive/materials/using/reflectionTexture#using-local-cubemap-mode
  123. */
  124. this.boundingBoxPosition = Vector3.Zero();
  125. this._rotationY = 0;
  126. /** @internal */
  127. this._files = null;
  128. this._forcedExtension = null;
  129. this._extensions = null;
  130. this._textureMatrixRefraction = new Matrix();
  131. this.name = rootUrl;
  132. this.url = rootUrl;
  133. this._noMipmap = noMipmap;
  134. this.hasAlpha = false;
  135. this._format = format;
  136. this.isCube = true;
  137. this._textureMatrix = Matrix.Identity();
  138. this._createPolynomials = createPolynomials;
  139. this.coordinatesMode = Texture.CUBIC_MODE;
  140. this._extensions = extensions;
  141. this._files = files;
  142. this._forcedExtension = forcedExtension;
  143. this._loaderOptions = loaderOptions;
  144. this._useSRGBBuffer = useSRGBBuffer;
  145. this._lodScale = lodScale;
  146. this._lodOffset = lodOffset;
  147. if (!rootUrl && !files) {
  148. return;
  149. }
  150. this.updateURL(rootUrl, forcedExtension, onLoad, prefiltered, onError, extensions, this.getScene()?.useDelayedTextureLoading, files);
  151. }
  152. /**
  153. * Get the current class name of the texture useful for serialization or dynamic coding.
  154. * @returns "CubeTexture"
  155. */
  156. getClassName() {
  157. return "CubeTexture";
  158. }
  159. /**
  160. * Update the url (and optional buffer) of this texture if url was null during construction.
  161. * @param url the url of the texture
  162. * @param forcedExtension defines the extension to use
  163. * @param onLoad callback called when the texture is loaded (defaults to null)
  164. * @param prefiltered Defines whether the updated texture is prefiltered or not
  165. * @param onError callback called if there was an error during the loading process (defaults to null)
  166. * @param extensions defines the suffixes add to the picture name in case six images are in use like _px.jpg...
  167. * @param delayLoad defines if the texture should be loaded now (false by default)
  168. * @param files defines the six files to load for the different faces in that order: px, py, pz, nx, ny, nz
  169. */
  170. updateURL(url, forcedExtension, onLoad = null, prefiltered = false, onError = null, extensions = null, delayLoad = false, files = null) {
  171. if (!this.name || this.name.startsWith("data:")) {
  172. this.name = url;
  173. }
  174. this.url = url;
  175. if (forcedExtension) {
  176. this._forcedExtension = forcedExtension;
  177. }
  178. const lastDot = url.lastIndexOf(".");
  179. const extension = forcedExtension ? forcedExtension : lastDot > -1 ? url.substring(lastDot).toLowerCase() : "";
  180. const isDDS = extension.indexOf(".dds") === 0;
  181. const isEnv = extension.indexOf(".env") === 0;
  182. const isBasis = extension.indexOf(".basis") === 0;
  183. if (isEnv) {
  184. this.gammaSpace = false;
  185. this._prefiltered = false;
  186. this.anisotropicFilteringLevel = 1;
  187. }
  188. else {
  189. this._prefiltered = prefiltered;
  190. if (prefiltered) {
  191. this.gammaSpace = false;
  192. this.anisotropicFilteringLevel = 1;
  193. }
  194. }
  195. if (files) {
  196. this._files = files;
  197. }
  198. else {
  199. if (!isBasis && !isEnv && !isDDS && !extensions) {
  200. extensions = ["_px.jpg", "_py.jpg", "_pz.jpg", "_nx.jpg", "_ny.jpg", "_nz.jpg"];
  201. }
  202. this._files = this._files || [];
  203. this._files.length = 0;
  204. if (extensions) {
  205. for (let index = 0; index < extensions.length; index++) {
  206. this._files.push(url + extensions[index]);
  207. }
  208. this._extensions = extensions;
  209. }
  210. }
  211. if (delayLoad) {
  212. this.delayLoadState = 4;
  213. this._delayedOnLoad = onLoad;
  214. this._delayedOnError = onError;
  215. }
  216. else {
  217. this._loadTexture(onLoad, onError);
  218. }
  219. }
  220. /**
  221. * Delays loading of the cube texture
  222. * @param forcedExtension defines the extension to use
  223. */
  224. delayLoad(forcedExtension) {
  225. if (this.delayLoadState !== 4) {
  226. return;
  227. }
  228. if (forcedExtension) {
  229. this._forcedExtension = forcedExtension;
  230. }
  231. this.delayLoadState = 1;
  232. this._loadTexture(this._delayedOnLoad, this._delayedOnError);
  233. }
  234. /**
  235. * Returns the reflection texture matrix
  236. * @returns the reflection texture matrix
  237. */
  238. getReflectionTextureMatrix() {
  239. return this._textureMatrix;
  240. }
  241. /**
  242. * Sets the reflection texture matrix
  243. * @param value Reflection texture matrix
  244. */
  245. setReflectionTextureMatrix(value) {
  246. if (value.updateFlag === this._textureMatrix.updateFlag) {
  247. return;
  248. }
  249. if (value.isIdentity() !== this._textureMatrix.isIdentity()) {
  250. this.getScene()?.markAllMaterialsAsDirty(1, (mat) => mat.getActiveTextures().indexOf(this) !== -1);
  251. }
  252. this._textureMatrix = value;
  253. if (!this.getScene()?.useRightHandedSystem) {
  254. return;
  255. }
  256. const scale = TmpVectors.Vector3[0];
  257. const quat = TmpVectors.Quaternion[0];
  258. const trans = TmpVectors.Vector3[1];
  259. this._textureMatrix.decompose(scale, quat, trans);
  260. quat.z *= -1; // these two operations correspond to negating the x and y euler angles
  261. quat.w *= -1;
  262. Matrix.ComposeToRef(scale, quat, trans, this._textureMatrixRefraction);
  263. }
  264. /**
  265. * Gets a suitable rotate/transform matrix when the texture is used for refraction.
  266. * There's a separate function from getReflectionTextureMatrix because refraction requires a special configuration of the matrix in right-handed mode.
  267. * @returns The refraction matrix
  268. */
  269. getRefractionTextureMatrix() {
  270. return this.getScene()?.useRightHandedSystem ? this._textureMatrixRefraction : this._textureMatrix;
  271. }
  272. _loadTexture(onLoad = null, onError = null) {
  273. const scene = this.getScene();
  274. const oldTexture = this._texture;
  275. this._texture = this._getFromCache(this.url, this._noMipmap, undefined, undefined, this._useSRGBBuffer, this.isCube);
  276. const onLoadProcessing = () => {
  277. this.onLoadObservable.notifyObservers(this);
  278. if (oldTexture) {
  279. oldTexture.dispose();
  280. this.getScene()?.markAllMaterialsAsDirty(1);
  281. }
  282. if (onLoad) {
  283. onLoad();
  284. }
  285. };
  286. const errorHandler = (message, exception) => {
  287. this._loadingError = true;
  288. this._errorObject = { message, exception };
  289. if (onError) {
  290. onError(message, exception);
  291. }
  292. Texture.OnTextureLoadErrorObservable.notifyObservers(this);
  293. };
  294. if (!this._texture) {
  295. if (this._prefiltered) {
  296. this._texture = this._getEngine().createPrefilteredCubeTexture(this.url, scene, this._lodScale, this._lodOffset, onLoad, errorHandler, this._format, this._forcedExtension, this._createPolynomials);
  297. }
  298. else {
  299. this._texture = this._getEngine().createCubeTexture(this.url, scene, this._files, this._noMipmap, onLoad, errorHandler, this._format, this._forcedExtension, false, this._lodScale, this._lodOffset, null, this._loaderOptions, !!this._useSRGBBuffer);
  300. }
  301. this._texture?.onLoadedObservable.add(() => this.onLoadObservable.notifyObservers(this));
  302. }
  303. else {
  304. if (this._texture.isReady) {
  305. Tools.SetImmediate(() => onLoadProcessing());
  306. }
  307. else {
  308. this._texture.onLoadedObservable.add(() => onLoadProcessing());
  309. }
  310. }
  311. }
  312. /**
  313. * Parses text to create a cube texture
  314. * @param parsedTexture define the serialized text to read from
  315. * @param scene defines the hosting scene
  316. * @param rootUrl defines the root url of the cube texture
  317. * @returns a cube texture
  318. */
  319. static Parse(parsedTexture, scene, rootUrl) {
  320. const texture = SerializationHelper.Parse(() => {
  321. let prefiltered = false;
  322. if (parsedTexture.prefiltered) {
  323. prefiltered = parsedTexture.prefiltered;
  324. }
  325. return new CubeTexture(rootUrl + (parsedTexture.url ?? parsedTexture.name), scene, parsedTexture.extensions, false, parsedTexture.files || null, null, null, undefined, prefiltered, parsedTexture.forcedExtension);
  326. }, parsedTexture, scene);
  327. // Local Cubemaps
  328. if (parsedTexture.boundingBoxPosition) {
  329. texture.boundingBoxPosition = Vector3.FromArray(parsedTexture.boundingBoxPosition);
  330. }
  331. if (parsedTexture.boundingBoxSize) {
  332. texture.boundingBoxSize = Vector3.FromArray(parsedTexture.boundingBoxSize);
  333. }
  334. // Animations
  335. if (parsedTexture.animations) {
  336. for (let animationIndex = 0; animationIndex < parsedTexture.animations.length; animationIndex++) {
  337. const parsedAnimation = parsedTexture.animations[animationIndex];
  338. const internalClass = GetClass("BABYLON.Animation");
  339. if (internalClass) {
  340. texture.animations.push(internalClass.Parse(parsedAnimation));
  341. }
  342. }
  343. }
  344. return texture;
  345. }
  346. /**
  347. * Makes a clone, or deep copy, of the cube texture
  348. * @returns a new cube texture
  349. */
  350. clone() {
  351. let uniqueId = 0;
  352. const newCubeTexture = SerializationHelper.Clone(() => {
  353. const cubeTexture = new CubeTexture(this.url, this.getScene() || this._getEngine(), this._extensions, this._noMipmap, this._files);
  354. uniqueId = cubeTexture.uniqueId;
  355. return cubeTexture;
  356. }, this);
  357. newCubeTexture.uniqueId = uniqueId;
  358. return newCubeTexture;
  359. }
  360. }
  361. __decorate([
  362. serialize()
  363. ], CubeTexture.prototype, "url", void 0);
  364. __decorate([
  365. serializeAsVector3()
  366. ], CubeTexture.prototype, "boundingBoxPosition", void 0);
  367. __decorate([
  368. serializeAsVector3()
  369. ], CubeTexture.prototype, "boundingBoxSize", null);
  370. __decorate([
  371. serialize("rotationY")
  372. ], CubeTexture.prototype, "rotationY", null);
  373. __decorate([
  374. serialize("files")
  375. ], CubeTexture.prototype, "_files", void 0);
  376. __decorate([
  377. serialize("forcedExtension")
  378. ], CubeTexture.prototype, "_forcedExtension", void 0);
  379. __decorate([
  380. serialize("extensions")
  381. ], CubeTexture.prototype, "_extensions", void 0);
  382. __decorate([
  383. serializeAsMatrix("textureMatrix")
  384. ], CubeTexture.prototype, "_textureMatrix", void 0);
  385. __decorate([
  386. serializeAsMatrix("textureMatrixRefraction")
  387. ], CubeTexture.prototype, "_textureMatrixRefraction", void 0);
  388. Texture._CubeTextureParser = CubeTexture.Parse;
  389. // Some exporters relies on Tools.Instantiate
  390. RegisterClass("BABYLON.CubeTexture", CubeTexture);
  391. //# sourceMappingURL=cubeTexture.js.map