Extensions

Source: textures/Texture.js

import { TextureInfo } from "./TextureInfo";

const _placeholderImage = document.createElement("img");

const _createElement = (tag, src) => {
  const element = document.createElement(tag);
  element.crossOrigin = "anonymous";
  element.src = src;
  return element;
};

/**
 * Texture
 * @extends {TextureInfo}
 * @property {boolean} isVideo
 * @property {boolean} shouldUpdate
 */
export class Texture extends TextureInfo {
  /**
   * Creates an instance of Texture.
   * @constructor
   * @param {HTMLElement} source - The source of the texture
   * @param {boolean} shouldUpdate - Whether the texture should update every frame
   */
  constructor(source, shouldUpdate) {
    super();

    this._source = _placeholderImage;

    this.updateSize = this.updateSize.bind(this);

    this.source = source;
    this.shouldUpdate = shouldUpdate;

    this._currentRenderTime = 0;
  }

  /**
   * Set/Get source of the texture
   * @type {HTMLElement}
   */
  get source() {
    return this._source;
  }

  set source(value) {
    this.destruct();

    if (value) {
      this._source = value;

      this.isVideo = value.tagName.toLowerCase() === "video";
      this._eventType = this.isVideo ? "canplay" : "load";

      !this.updateSize() &&
        value.addEventListener(this._eventType, this.updateSize, {
          once: true,
        });
    }
  }

  /**
   * Destruct the class
   */
  destruct() {
    this._source &&
      this._source.removeEventListener(this._eventType, this.updateSize);
    this.$renderSource = null;
  }

  /**
   * Use TextureInfo
   * @param {WebGLContext} gl - The WebGL context
   * @param {number} id - The texture id
   * @param {boolean} forceBind - Force bind
   * @param {number} renderTime - The current render time
   */
  use(gl, id, forceBind, renderTime) {
    if (this.$currentAglId < gl.gl_id) {
      this.$currentAglId = gl.gl_id;
      this._baseTexture = gl.createTexture();
      this.useActiveTexture(gl, id);
    } else if (
      this.$updated ||
      this._loaded ||
      (this.shouldUpdate && this._currentRenderTime < renderTime) ||
      (this.isVideo && !this._source.paused)
    ) {
      this.$updated = this._loaded = false;
      this._currentRenderTime = renderTime;
      this.useActiveTexture(gl, id);
    } else if (this._currentActiveId !== id || forceBind)
      this.bindActiveTexture(gl, id);
  }

  /**
   * Update texture size
   * @returns {boolean}
   */
  updateSize() {
    const source = this._source,
      sourceWidth = source.videoWidth || source.naturalWidth || source.width,
      sourceHeight =
        source.videoHeight || source.naturalHeight || source.height;

    if (sourceWidth * sourceHeight) {
      this.$width = sourceWidth;
      this.$height = sourceHeight;
      this.$renderSource = this._source;
      return this._loaded = this.$updated = true;
    }
  }
}

/**
 * Create a new Texture from an image source
 * @function
 * @param {HTMLElement} src - The source of the texture
 * @param {boolean} shouldUpdate - Whether the texture should update every frame
 * @returns {Texture}
 */
Texture.loadImage = (src, shouldUpdate) =>
  new Texture(_createElement("img", src), shouldUpdate);

/**
 * Create a new Texture from a video source
 * @function
 * @param {HTMLVideoElement} src - The source of the texture
 * @returns {Texture}
 */
Texture.loadVideo = (src) => new Texture(_createElement("video", src));