/home/lindsay/xeolabs/xeogl-next/xeogl/src/materials/texture.js
API Docs for:

File: /home/lindsay/xeolabs/xeogl-next/xeogl/src/materials/texture.js

  1. /**
  2. A **Texture** specifies a texture map.
  3.  
  4. ## Overview
  5.  
  6. * Textures are grouped within {{#crossLink "Material"}}Materials{{/crossLink}}, which are attached to
  7. {{#crossLink "Mesh"}}Meshes{{/crossLink}}.
  8. * To create a Texture from an image file, set the Texture's {{#crossLink "Texture/src:property"}}{{/crossLink}}
  9. property to the image file path.
  10. * To create a Texture from an HTMLImageElement, set the Texture's {{#crossLink "Texture/image:property"}}{{/crossLink}}
  11. property to the HTMLImageElement.
  12.  
  13. ## Examples
  14.  
  15. * [Textures on MetallicMaterials](../../examples/#materials_metallic_textures)
  16. * [Textures on SpecularMaterials](../../examples/#materials_specGloss_textures)
  17. * [Textures on PhongMaterials](../../examples/#materials_phong_textures)
  18. * [Video texture](../../examples/#materials_phong_textures_video)
  19.  
  20. ## Usage
  21.  
  22. In this example we have a Mesh with
  23.  
  24. * a {{#crossLink "PhongMaterial"}}{{/crossLink}} which applies diffuse and specular {{#crossLink "Texture"}}Textures{{/crossLink}}, and
  25. * a {{#crossLink "TorusGeometry"}}{{/crossLink}}.
  26.  
  27. Note that xeogl will ignore the {{#crossLink "PhongMaterial"}}PhongMaterial's{{/crossLink}} {{#crossLink "PhongMaterial/diffuse:property"}}{{/crossLink}}
  28. and {{#crossLink "PhongMaterial/specular:property"}}{{/crossLink}} properties, since we assigned {{#crossLink "Texture"}}Textures{{/crossLink}} to the {{#crossLink "PhongMaterial"}}PhongMaterial's{{/crossLink}} {{#crossLink "PhongMaterial/diffuseMap:property"}}{{/crossLink}} and
  29. {{#crossLink "PhongMaterial/specularMap:property"}}{{/crossLink}} properties. The {{#crossLink "Texture"}}Textures'{{/crossLink}} pixel
  30. colors directly provide the diffuse and specular components for each fragment across the {{#crossLink "Geometry"}}{{/crossLink}} surface.
  31.  
  32. ```` javascript
  33. var mesh = new xeogl.Mesh({
  34.  
  35. material: new xeogl.PhongMaterial({
  36. ambient: [0.3, 0.3, 0.3],
  37. diffuse: [0.5, 0.5, 0.0], // Ignored, since we have assigned a Texture to diffuseMap, below
  38. specular: [1.0, 1.0, 1.0], // Ignored, since we have assigned a Texture to specularMap, below
  39. diffuseMap: new xeogl.Texture({
  40. src: "diffuseMap.jpg"
  41. }),
  42. specularMap: new xeogl.Fresnel({
  43. src: "diffuseMap.jpg"
  44. }),
  45. shininess: 80, // Default
  46. alpha: 1.0 // Default
  47. }),
  48.  
  49. geometry: new xeogl.TorusGeometry()
  50. });
  51. ````
  52.  
  53. @class Texture
  54. @module xeogl
  55. @submodule materials
  56. @constructor
  57. @param [owner] {Component} Owner component. When destroyed, the owner will destroy this component as well. Creates this component within the default {{#crossLink "Scene"}}{{/crossLink}} when omitted.
  58. @param [cfg] {*} Configs
  59. @param [cfg.id] {String} Optional ID for this Texture, unique among all components in the parent scene, generated automatically when omitted.
  60. @param [cfg.meta] {String:Object} Optional map of user-defined metadata to attach to this Texture.
  61. @param [cfg.src=null] {String} Path to image file to load into this Texture. See the {{#crossLink "Texture/src:property"}}{{/crossLink}} property for more info.
  62. @param [cfg.image=null] {HTMLImageElement} HTML Image object to load into this Texture. See the {{#crossLink "Texture/image:property"}}{{/crossLink}} property for more info.
  63. @param [cfg.minFilter="linearMipmapLinear"] {String} How the texture is sampled when a texel covers less than one pixel. See the {{#crossLink "Texture/minFilter:property"}}{{/crossLink}} property for more info.
  64. @param [cfg.magFilter="linear"] {String} How the texture is sampled when a texel covers more than one pixel. See the {{#crossLink "Texture/magFilter:property"}}{{/crossLink}} property for more info.
  65. @param [cfg.wrapS="repeat"] {String} Wrap parameter for texture coordinate *S*. See the {{#crossLink "Texture/wrapS:property"}}{{/crossLink}} property for more info.
  66. @param [cfg.wrapT="repeat"] {String} Wrap parameter for texture coordinate *S*. See the {{#crossLink "Texture/wrapT:property"}}{{/crossLink}} property for more info.
  67. @param [cfg.flipY=false] {Boolean} Flips this Texture's source data along its vertical axis when true.
  68. @param [cfg.translate=[0,0]] {Array of Number} 2D translation vector that will be added to texture's *S* and *T* coordinates.
  69. @param [cfg.scale=[1,1]] {Array of Number} 2D scaling vector that will be applied to texture's *S* and *T* coordinates.
  70. @param [cfg.rotate=0] {Number} Rotation, in degrees, that will be applied to texture's *S* and *T* coordinates.
  71. @param [cfg.encoding="linear"] {String} Encoding format. See the {{#crossLink "Texture/encoding:property"}}{{/crossLink}} property for more info.
  72. @extends Component
  73. */
  74. import {core} from "./../core.js";
  75. import {Component} from '../component.js';
  76. import {State} from '../renderer/state.js';
  77. import {Texture2D} from '../renderer/texture2d.js';
  78. import {math} from '../math/math.js';
  79. import {stats} from './../stats.js';
  80. import {componentClasses} from "./../componentClasses.js";
  81.  
  82. const type = "xeogl.Texture";
  83.  
  84. function ensureImageSizePowerOfTwo(image) {
  85. if (!isPowerOfTwo(image.width) || !isPowerOfTwo(image.height)) {
  86. const canvas = document.createElement("canvas");
  87. canvas.width = nextHighestPowerOfTwo(image.width);
  88. canvas.height = nextHighestPowerOfTwo(image.height);
  89. const ctx = canvas.getContext("2d");
  90. ctx.drawImage(image,
  91. 0, 0, image.width, image.height,
  92. 0, 0, canvas.width, canvas.height);
  93. image = canvas;
  94. }
  95. return image;
  96. }
  97.  
  98. function isPowerOfTwo(x) {
  99. return (x & (x - 1)) === 0;
  100. }
  101.  
  102. function nextHighestPowerOfTwo(x) {
  103. --x;
  104. for (let i = 1; i < 32; i <<= 1) {
  105. x = x | x >> i;
  106. }
  107. return x + 1;
  108. }
  109.  
  110. class Texture extends Component {
  111.  
  112. /**
  113. JavaScript class name for this Component.
  114.  
  115. For example: "xeogl.AmbientLight", "xeogl.MetallicMaterial" etc.
  116.  
  117. @property type
  118. @type String
  119. @final
  120. */
  121. get type() {
  122. return type;
  123. }
  124.  
  125. init(cfg) {
  126.  
  127. super.init(cfg);
  128.  
  129. this._state = new State({
  130. texture: new Texture2D(this.scene.canvas.gl),
  131. matrix: math.identityMat4(), // Float32Array
  132. hasMatrix: (cfg.translate && (cfg.translate[0] !== 0 || cfg.translate[1] !== 0)) || (!!cfg.rotate) || (cfg.scale && (cfg.scale[0] !== 0 || cfg.scale[1] !== 0)),
  133. minFilter: this._checkMinFilter(cfg.minFilter),
  134. magFilter: this._checkMagFilter(cfg.minFilter),
  135. wrapS: this._checkWrapS(cfg.wrapS),
  136. wrapT: this._checkWrapT(cfg.wrapT),
  137. flipY: this._checkFlipY(cfg.flipY),
  138. encoding: this._checkEncoding(cfg.encoding)
  139. });
  140.  
  141. // Data source
  142.  
  143. this._src = null;
  144. this._image = null;
  145.  
  146. // Transformation
  147.  
  148. this._translate = math.vec2([0, 0]);
  149. this._scale = math.vec2([1, 1]);
  150. this._rotate = math.vec2([0, 0]);
  151.  
  152. this._matrixDirty = false;
  153.  
  154. // Transform
  155.  
  156. this.translate = cfg.translate;
  157. this.scale = cfg.scale;
  158. this.rotate = cfg.rotate;
  159.  
  160. // Data source
  161.  
  162. if (cfg.src) {
  163. this.src = cfg.src; // Image file
  164. } else if (cfg.image) {
  165. this.image = cfg.image; // Image object
  166. }
  167.  
  168. stats.memory.textures++;
  169. }
  170.  
  171. _checkMinFilter(value) {
  172. value = value || "linearMipmapLinear";
  173. if (value !== "linear" &&
  174. value !== "linearMipmapNearest" &&
  175. value !== "linearMipmapLinear" &&
  176. value !== "nearestMipmapLinear" &&
  177. value !== "nearestMipmapNearest") {
  178. this.error("Unsupported value for 'minFilter': '" + value +
  179. "' - supported values are 'linear', 'linearMipmapNearest', 'nearestMipmapNearest', " +
  180. "'nearestMipmapLinear' and 'linearMipmapLinear'. Defaulting to 'linearMipmapLinear'.");
  181. value = "linearMipmapLinear";
  182. }
  183. return value;
  184. }
  185.  
  186. _checkMagFilter(value) {
  187. value = value || "linear";
  188. if (value !== "linear" && value !== "nearest") {
  189. this.error("Unsupported value for 'magFilter': '" + value +
  190. "' - supported values are 'linear' and 'nearest'. Defaulting to 'linear'.");
  191. value = "linear";
  192. }
  193. return value;
  194. }
  195.  
  196. _checkFilter(value) {
  197. value = value || "linear";
  198. if (value !== "linear" && value !== "nearest") {
  199. this.error("Unsupported value for 'magFilter': '" + value +
  200. "' - supported values are 'linear' and 'nearest'. Defaulting to 'linear'.");
  201. value = "linear";
  202. }
  203. return value;
  204. }
  205.  
  206. _checkWrapS(value) {
  207. value = value || "repeat";
  208. if (value !== "clampToEdge" && value !== "mirroredRepeat" && value !== "repeat") {
  209. this.error("Unsupported value for 'wrapS': '" + value +
  210. "' - supported values are 'clampToEdge', 'mirroredRepeat' and 'repeat'. Defaulting to 'repeat'.");
  211. value = "repeat";
  212. }
  213. return value;
  214. }
  215.  
  216. _checkWrapT(value) {
  217. value = value || "repeat";
  218. if (value !== "clampToEdge" && value !== "mirroredRepeat" && value !== "repeat") {
  219. this.error("Unsupported value for 'wrapT': '" + value +
  220. "' - supported values are 'clampToEdge', 'mirroredRepeat' and 'repeat'. Defaulting to 'repeat'.");
  221. value = "repeat";
  222. }
  223. return value;
  224. }
  225.  
  226. _checkFlipY(value) {
  227. return !!value;
  228. }
  229.  
  230. _checkEncoding(value) {
  231. value = value || "linear";
  232. if (value !== "linear" && value !== "sRGB" && value !== "gamma") {
  233. this.error("Unsupported value for 'encoding': '" + value + "' - supported values are 'linear', 'sRGB', 'gamma'. Defaulting to 'linear'.");
  234. value = "linear";
  235. }
  236. return value;
  237. }
  238.  
  239. _webglContextRestored() {
  240. this._state.texture = new Texture2D(this.scene.canvas.gl);
  241. if (this._image) {
  242. this.image = this._image;
  243. } else if (this._src) {
  244. this.src = this._src;
  245. }
  246. }
  247.  
  248. _update() {
  249. const state = this._state;
  250. if (this._matrixDirty) {
  251. let matrix;
  252. let t;
  253. if (this._translate[0] !== 0 || this._translate[1] !== 0) {
  254. matrix = math.translationMat4v([this._translate[0], this._translate[1], 0], this._state.matrix);
  255. }
  256. if (this._scale[0] !== 1 || this._scale[1] !== 1) {
  257. t = math.scalingMat4v([this._scale[0], this._scale[1], 1]);
  258. matrix = matrix ? math.mulMat4(matrix, t) : t;
  259. }
  260. if (this._rotate !== 0) {
  261. t = math.rotationMat4v(this._rotate * 0.0174532925, [0, 0, 1]);
  262. matrix = matrix ? math.mulMat4(matrix, t) : t;
  263. }
  264. if (matrix) {
  265. state.matrix = matrix;
  266. }
  267. this._matrixDirty = false;
  268. }
  269. this._renderer.imageDirty();
  270. }
  271.  
  272.  
  273. /**
  274. Indicates an HTML DOM Image object to source this Texture from.
  275.  
  276. Sets the {{#crossLink "Texture/src:property"}}{{/crossLink}} property to null.
  277.  
  278. @property image
  279. @default null
  280. @type {HTMLImageElement}
  281. */
  282. set image(value) {
  283. this._image = ensureImageSizePowerOfTwo(value);
  284. this._image.crossOrigin = "Anonymous";
  285. this._state.texture.setImage(this._image, this._state);
  286. this._state.texture.setProps(this._state); // Generate mipmaps
  287. this._src = null;
  288. this._renderer.imageDirty();
  289. }
  290.  
  291. get image() {
  292. return this._image;
  293. }
  294.  
  295. /**
  296. Indicates a path to an image file to source this Texture from.
  297.  
  298. Sets the {{#crossLink "Texture/image:property"}}{{/crossLink}} property to null.
  299.  
  300. @property src
  301. @default null
  302. @type String
  303. */
  304. set src(src) {
  305. this.scene.loading++;
  306. this.scene.canvas.spinner.processes++;
  307. const self = this;
  308. let image = new Image();
  309. image.onload = function () {
  310. image = ensureImageSizePowerOfTwo(image);
  311. //self._image = image; // For faster WebGL context restore - memory inefficient?
  312. self._state.texture.setImage(image, self._state);
  313. self._state.texture.setProps(self._state); // Generate mipmaps
  314. self.scene.loading--;
  315. self.scene.canvas.spinner.processes--;
  316. self._renderer.imageDirty();
  317. };
  318. image.src = src;
  319. this._src = src;
  320. this._image = null;
  321. }
  322.  
  323. get src() {
  324. return this._src;
  325. }
  326.  
  327. /**
  328. 2D translation vector that will be added to this Texture's *S* and *T* coordinates.
  329.  
  330. @property translate
  331. @default [0, 0]
  332. @type Array(Number)
  333. */
  334. set translate(value) {
  335. this._translate.set(value || [0, 0]);
  336. this._matrixDirty = true;
  337. this._needUpdate();
  338. }
  339.  
  340. get translate() {
  341. return this._translate;
  342. }
  343.  
  344. /**
  345. 2D scaling vector that will be applied to this Texture's *S* and *T* coordinates.
  346.  
  347. @property scale
  348. @default [1, 1]
  349. @type Array(Number)
  350. */
  351. set scale(value) {
  352. this._scale.set(value || [1, 1]);
  353. this._matrixDirty = true;
  354. this._needUpdate();
  355. }
  356.  
  357. get scale() {
  358. return this._scale;
  359. }
  360.  
  361. /**
  362. Rotation, in degrees, that will be applied to this Texture's *S* and *T* coordinates.
  363.  
  364. @property rotate
  365. @default 0
  366. @type Number
  367. */
  368. set rotate(value) {
  369. value = value || 0;
  370. if (this._rotate === value) {
  371. return;
  372. }
  373. this._rotate = value;
  374. this._matrixDirty = true;
  375. this._needUpdate();
  376. }
  377.  
  378. get rotate() {
  379. return this._rotate;
  380. }
  381.  
  382. /**
  383. How this Texture is sampled when a texel covers less than one pixel.
  384.  
  385. Options are:
  386.  
  387. * **"nearest"** - Uses the value of the texture element that is nearest
  388. (in Manhattan distance) to the center of the pixel being textured.
  389.  
  390. * **"linear"** - Uses the weighted average of the four texture elements that are
  391. closest to the center of the pixel being textured.
  392.  
  393. * **"nearestMipmapNearest"** - Chooses the mipmap that most closely matches the
  394. size of the pixel being textured and uses the "nearest" criterion (the texture
  395. element nearest to the center of the pixel) to produce a texture value.
  396.  
  397. * **"linearMipmapNearest"** - Chooses the mipmap that most closely matches the size of
  398. the pixel being textured and uses the "linear" criterion (a weighted average of the
  399. four texture elements that are closest to the center of the pixel) to produce a
  400. texture value.
  401.  
  402. * **"nearestMipmapLinear"** - Chooses the two mipmaps that most closely
  403. match the size of the pixel being textured and uses the "nearest" criterion
  404. (the texture element nearest to the center of the pixel) to produce a texture
  405. value from each mipmap. The final texture value is a weighted average of those two
  406. values.
  407.  
  408. * **"linearMipmapLinear"** - **(default)** - Chooses the two mipmaps that most closely match the size
  409. of the pixel being textured and uses the "linear" criterion (a weighted average
  410. of the four texture elements that are closest to the center of the pixel) to
  411. produce a texture value from each mipmap. The final texture value is a weighted
  412. average of those two values.
  413.  
  414. @property minFilter
  415. @default "linearMipmapLinear"
  416. @type String
  417. @final
  418. */
  419. get minFilter() {
  420. return this._state.minFilter;
  421. }
  422.  
  423. /**
  424. How this Texture is sampled when a texel covers more than one pixel.
  425.  
  426. Options are:
  427.  
  428. * **"nearest"** - Uses the value of the texture element that is nearest
  429. (in Manhattan distance) to the center of the pixel being textured.
  430. * **"linear"** - **(default)** - Uses the weighted average of the four texture elements that are
  431. closest to the center of the pixel being textured.
  432.  
  433. @property magFilter
  434. @default "linear"
  435. @type String
  436. @final
  437. */
  438. get magFilter() {
  439. return this._state.magFilter;
  440. }
  441.  
  442. /**
  443. Wrap parameter for this Texture's *S* coordinate.
  444.  
  445. Options are:
  446.  
  447. * **"clampToEdge"** - causes *S* coordinates to be clamped to the size of the texture.
  448. * **"mirroredRepeat"** - causes the *S* coordinate to be set to the fractional part of the texture coordinate
  449. if the integer part of *S* is even; if the integer part of *S* is odd, then the *S* texture coordinate is
  450. set to *1 - frac ⁡ S* , where *frac ⁡ S* represents the fractional part of *S*.
  451. * **"repeat"** - **(default)** - causes the integer part of the *S* coordinate to be ignored; xeogl uses only the
  452. fractional part, thereby creating a repeating pattern.
  453.  
  454. @property wrapS
  455. @default "repeat"
  456. @type String
  457. @final
  458. */
  459. get wrapS() {
  460. return this._state.wrapS;
  461. }
  462.  
  463. /**
  464. Wrap parameter for this Texture's *T* coordinate.
  465.  
  466. Options are:
  467.  
  468. * **"clampToEdge"** - Causes *T* coordinates to be clamped to the size of the texture.
  469. * **"mirroredRepeat"** - Causes the *T* coordinate to be set to the fractional part of the texture coordinate
  470. if the integer part of *T* is even; if the integer part of *T* is odd, then the *T* texture coordinate is
  471. set to *1 - frac ⁡ S* , where *frac ⁡ S* represents the fractional part of *T*.
  472. * **"repeat"** - **(default)** - Causes the integer part of the *T* coordinate to be ignored; xeogl uses only the
  473. fractional part, thereby creating a repeating pattern.
  474.  
  475. @property wrapT
  476. @default "repeat"
  477. @type String
  478. @final
  479. */
  480. get wrapT() {
  481. return this._state.wrapT;
  482. }
  483.  
  484. /**
  485. Flips this Texture's source data along its vertical axis when true.
  486.  
  487. @property flipY
  488. @type Boolean
  489. @final
  490. */
  491. get flipY() {
  492. return this._state.flipY;
  493. }
  494.  
  495. /**
  496. The Texture's encoding format.
  497.  
  498. @property encoding
  499. @type String
  500. @final
  501. */
  502. get encoding() {
  503. return this._state.encoding;
  504. }
  505.  
  506. destroy() {
  507. super.destroy();
  508. if (this._state.texture) {
  509. this._state.texture.destroy();
  510. }
  511. this._state.destroy();
  512. stats.memory.textures--;
  513. }
  514. }
  515.  
  516. componentClasses[type] = Texture;
  517.  
  518. export{Texture};