Source: gfx/mesh/Mesh.js

const Vector = require('engine/Vector');
const { Polygon, Rectangle } = require('../core/math');
const Node = require('../core/Node');
const CONST = require('../const');

const tempPoint = new Vector();
const tempPolygon = new Polygon();

/**
 * Base mesh class
 * @class
 * @extends Node
 * @memberof mesh
 * @param texture {Texture} The texture to use
 * @param [vertices] {Float32Array} if you want to specify the vertices
 * @param [uvs] {Float32Array} if you want to specify the uvs
 * @param [indices] {Uint16Array} if you want to specify the indices
 * @param [drawMode] {number} the drawMode, can be any of the Mesh.DRAW_MODES consts
 */
class Mesh extends Node {
  constructor(texture, vertices, uvs, indices, drawMode) {
    super();

      /**
       * The texture of the Mesh
       *
       * @member {Texture}
       * @private
       */
    this._texture = null;

      /**
       * The Uvs of the Mesh
       *
       * @member {Float32Array}
       */
    this.uvs = uvs || new Float32Array([0, 0,
      1, 0,
      1, 1,
      0, 1]);

      /**
       * An array of vertices
       *
       * @member {Float32Array}
       */
    this.vertices = vertices || new Float32Array([0, 0,
      100, 0,
      100, 100,
      0, 100]);

      /*
       * @member {Uint16Array} An array containing the indices of the vertices
       */
      //  TODO auto generate this based on draw mode!
    this.indices = indices || new Uint16Array([0, 1, 3, 2]);

      /**
       * Whether the Mesh is dirty or not
       *
       * @member {boolean}
       */
    this.dirty = true;

      /**
       * The blend mode to be applied to the sprite. Set to `BLEND_MODES.NORMAL` to remove any blend mode.
       *
       * @member {number}
       * @default BLEND_MODES.NORMAL
       * @see BLEND_MODES
       */
    this.blendMode = CONST.BLEND_MODES.NORMAL;

      /**
       * Triangles in canvas mode are automatically antialiased, use this value to force triangles to overlap a bit with each other.
       *
       * @member {number}
       */
    this.canvasPadding = 0;

      /**
       * The way the Mesh should be drawn, can be any of the {@link mesh.Mesh.DRAW_MODES} consts
       *
       * @member {number}
       * @see mesh.Mesh.DRAW_MODES
       */
    this.drawMode = drawMode || Mesh.DRAW_MODES.TRIANGLE_MESH;

      // run texture setter;
    this.texture = texture;

       /**
       * The default shader that is used if a mesh doesn't have a more specific one.
       *
       * @member {Shader}
       */
    this.shader = null;
  }

  /**
   * Renders the object using the WebGL renderer
   *
   * @param renderer {WebGLRenderer} a reference to the WebGL renderer
   * @private
   */
  _renderWebGL(renderer) {
    renderer.setObjectRenderer(renderer.plugins.mesh);
    renderer.plugins.mesh.render(this);
  }

  /**
   * Renders the object using the Canvas renderer
   *
   * @param renderer {CanvasRenderer}
   * @private
   */
  _renderCanvas(renderer) {
    var context = renderer.context;

    var transform = this.worldTransform;
    var res = renderer.resolution;

    if (renderer.roundPixels) {
      context.setTransform(transform.a * res, transform.b * res, transform.c * res, transform.d * res, (transform.tx * res) | 0, (transform.ty * res) | 0);
    }
    else {
      context.setTransform(transform.a * res, transform.b * res, transform.c * res, transform.d * res, transform.tx * res, transform.ty * res);
    }

    if (this.drawMode === Mesh.DRAW_MODES.TRIANGLE_MESH) {
      this._renderCanvasTriangleMesh(context);
    }
    else {
      this._renderCanvasTriangles(context);
    }
  }

  /**
   * Draws the object in Triangle Mesh mode using canvas
   *
   * @param context {CanvasRenderingContext2D} the current drawing context
   * @private
   */
  _renderCanvasTriangleMesh(context) {
    // draw triangles!!
    var vertices = this.vertices;
    var uvs = this.uvs;

    var length = vertices.length / 2;
    // this.count++;

    for (var i = 0; i < length - 2; i++) {
      // draw some triangles!
      var index = i * 2;
      this._renderCanvasDrawTriangle(context, vertices, uvs, index, (index + 2), (index + 4));
    }
  }

  /**
   * Draws the object in triangle mode using canvas
   *
   * @param context {CanvasRenderingContext2D} the current drawing context
   * @private
   */
  _renderCanvasTriangles(context) {
      // draw triangles!!
    var vertices = this.vertices;
    var uvs = this.uvs;
    var indices = this.indices;

    var length = indices.length;
      // this.count++;

    for (var i = 0; i < length; i += 3) {
          // draw some triangles!
      var index0 = indices[i] * 2, index1 = indices[i + 1] * 2, index2 = indices[i + 2] * 2;
      this._renderCanvasDrawTriangle(context, vertices, uvs, index0, index1, index2);
    }
  }

  /**
   * Draws one of the triangles that form this Mesh
   *
   * @param context {CanvasRenderingContext2D} the current drawing context
   * @param vertices {Float32Array} a reference to the vertices of the Mesh
   * @param uvs {Float32Array} a reference to the uvs of the Mesh
   * @param index0 {number} the index of the first vertex
   * @param index1 {number} the index of the second vertex
   * @param index2 {number} the index of the third vertex
   * @private
   */
  _renderCanvasDrawTriangle(context, vertices, uvs, index0, index1, index2) {
    var base = this._texture.baseTexture;
    var textureSource = base.source;
    var textureWidth = base.width;
    var textureHeight = base.height;

    var x0 = vertices[index0], x1 = vertices[index1], x2 = vertices[index2];
    var y0 = vertices[index0 + 1], y1 = vertices[index1 + 1], y2 = vertices[index2 + 1];

    var u0 = uvs[index0] * base.width, u1 = uvs[index1] * base.width, u2 = uvs[index2] * base.width;
    var v0 = uvs[index0 + 1] * base.height, v1 = uvs[index1 + 1] * base.height, v2 = uvs[index2 + 1] * base.height;

    if (this.canvasPadding > 0) {
      var paddingX = this.canvasPadding / this.worldTransform.a;
      var paddingY = this.canvasPadding / this.worldTransform.d;
      var centerX = (x0 + x1 + x2) / 3;
      var centerY = (y0 + y1 + y2) / 3;

      var normX = x0 - centerX;
      var normY = y0 - centerY;

      var dist = Math.sqrt(normX * normX + normY * normY);
      x0 = centerX + (normX / dist) * (dist + paddingX);
      y0 = centerY + (normY / dist) * (dist + paddingY);

          //

      normX = x1 - centerX;
      normY = y1 - centerY;

      dist = Math.sqrt(normX * normX + normY * normY);
      x1 = centerX + (normX / dist) * (dist + paddingX);
      y1 = centerY + (normY / dist) * (dist + paddingY);

      normX = x2 - centerX;
      normY = y2 - centerY;

      dist = Math.sqrt(normX * normX + normY * normY);
      x2 = centerX + (normX / dist) * (dist + paddingX);
      y2 = centerY + (normY / dist) * (dist + paddingY);
    }

    context.save();
    context.beginPath();


    context.moveTo(x0, y0);
    context.lineTo(x1, y1);
    context.lineTo(x2, y2);

    context.closePath();

    context.clip();

      // Compute matrix transform
    var delta = (u0 * v1) + (v0 * u2) + (u1 * v2) - (v1 * u2) - (v0 * u1) - (u0 * v2);
    var deltaA = (x0 * v1) + (v0 * x2) + (x1 * v2) - (v1 * x2) - (v0 * x1) - (x0 * v2);
    var deltaB = (u0 * x1) + (x0 * u2) + (u1 * x2) - (x1 * u2) - (x0 * u1) - (u0 * x2);
    var deltaC = (u0 * v1 * x2) + (v0 * x1 * u2) + (x0 * u1 * v2) - (x0 * v1 * u2) - (v0 * u1 * x2) - (u0 * x1 * v2);
    var deltaD = (y0 * v1) + (v0 * y2) + (y1 * v2) - (v1 * y2) - (v0 * y1) - (y0 * v2);
    var deltaE = (u0 * y1) + (y0 * u2) + (u1 * y2) - (y1 * u2) - (y0 * u1) - (u0 * y2);
    var deltaF = (u0 * v1 * y2) + (v0 * y1 * u2) + (y0 * u1 * v2) - (y0 * v1 * u2) - (v0 * u1 * y2) - (u0 * y1 * v2);

    context.transform(deltaA / delta, deltaD / delta,
          deltaB / delta, deltaE / delta,
          deltaC / delta, deltaF / delta);

    context.drawImage(textureSource, 0, 0, textureWidth * base.resolution, textureHeight * base.resolution, 0, 0, textureWidth, textureHeight);
    context.restore();
  }



  /**
   * Renders a flat Mesh
   *
   * @param Mesh {mesh.Mesh} The Mesh to render
   * @private
   */
  renderMeshFlat(Mesh) {
    var context = this.context;
    var vertices = Mesh.vertices;

    var length = vertices.length / 2;
      // this.count++;

    context.beginPath();
    for (var i = 1; i < length - 2; i++) {
          // draw some triangles!
      var index = i * 2;

      var x0 = vertices[index], x1 = vertices[index + 2], x2 = vertices[index + 4];
      var y0 = vertices[index + 1], y1 = vertices[index + 3], y2 = vertices[index + 5];

      context.moveTo(x0, y0);
      context.lineTo(x1, y1);
      context.lineTo(x2, y2);
    }

    context.fillStyle = '#FF0000';
    context.fill();
    context.closePath();
  }

  /**
   * When the texture is updated, this event will fire to update the scale and frame
   *
   * @param event
   * @private
   */
  _onTextureUpdate() {
    this.updateFrame = true;
  }

  /**
   * Returns the bounds of the mesh as a rectangle. The bounds calculation takes the worldTransform into account.
   *
   * @param matrix {Matrix} the transformation matrix of the sprite
   * @return {Rectangle} the framing rectangle
   */
  getBounds(matrix) {
    if (!this._currentBounds) {
      var worldTransform = matrix || this.worldTransform;

      var a = worldTransform.a;
      var b = worldTransform.b;
      var c = worldTransform.c;
      var d = worldTransform.d;
      var tx = worldTransform.tx;
      var ty = worldTransform.ty;

      var maxX = -Infinity;
      var maxY = -Infinity;

      var minX = Infinity;
      var minY = Infinity;

      var vertices = this.vertices;
      for (var i = 0, n = vertices.length; i < n; i += 2) {
        var rawX = vertices[i], rawY = vertices[i + 1];
        var x = (a * rawX) + (c * rawY) + tx;
        var y = (d * rawY) + (b * rawX) + ty;

        minX = x < minX ? x : minX;
        minY = y < minY ? y : minY;

        maxX = x > maxX ? x : maxX;
        maxY = y > maxY ? y : maxY;
      }

      if (minX === -Infinity || maxY === Infinity) {
        return Rectangle.EMPTY;
      }

      var bounds = this._bounds;

      bounds.x = minX;
      bounds.width = maxX - minX;

      bounds.y = minY;
      bounds.height = maxY - minY;

          // store a reference so that if this function gets called again in the render cycle we do not have to recalculate
      this._currentBounds = bounds;
    }

    return this._currentBounds;
  }

  /**
   * Tests if a point is inside this mesh. Works only for TRIANGLE_MESH
   *
   * @param point {Point} the point to test
   * @return {boolean} the result of the test
   */
  containsPoint(point) {
    if (!this.getBounds().contains(point.x, point.y)) {
      return false;
    }
    this.worldTransform.applyInverse(point, tempPoint);

    var vertices = this.vertices;
    var points = tempPolygon.points;
    var i, len;

    if (this.drawMode === Mesh.DRAW_MODES.TRIANGLES) {
      var indices = this.indices;
      len = this.indices.length;
          // TODO: inline this.
      for (i = 0; i < len; i += 3) {
        var ind0 = indices[i] * 2, ind1 = indices[i + 1] * 2, ind2 = indices[i + 2] * 2;
        points[0] = vertices[ind0];
        points[1] = vertices[ind0 + 1];
        points[2] = vertices[ind1];
        points[3] = vertices[ind1 + 1];
        points[4] = vertices[ind2];
        points[5] = vertices[ind2 + 1];
        if (tempPolygon.contains(tempPoint.x, tempPoint.y)) {
          return true;
        }
      }
    }
    else {
      len = vertices.length;
      for (i = 0; i < len; i += 6) {
        points[0] = vertices[i];
        points[1] = vertices[i + 1];
        points[2] = vertices[i + 2];
        points[3] = vertices[i + 3];
        points[4] = vertices[i + 4];
        points[5] = vertices[i + 5];
        if (tempPolygon.contains(tempPoint.x, tempPoint.y)) {
          return true;
        }
      }
    }
    return false;
  }
}

Object.defineProperties(Mesh.prototype, {
    /**
     * The texture that the sprite is using
     *
     * @member {Texture}
     * @memberof mesh.Mesh#
     */
  texture: {
    get: function() {
      return this._texture;
    },
    set: function(value) {
      if (this._texture === value) {
        return;
      }

      this._texture = value;

      if (value) {
                // wait for the texture to load
        if (value.baseTexture.hasLoaded) {
          this._onTextureUpdate();
        }
        else {
          value.once('update', this._onTextureUpdate, this);
        }
      }
    },
  },
});

/**
 * Different drawing buffer modes supported
 *
 * @static
 * @constant
 * @property {object} DRAW_MODES
 * @property {number} DRAW_MODES.TRIANGLE_MESH
 * @property {number} DRAW_MODES.TRIANGLES
 */
Mesh.DRAW_MODES = {
  TRIANGLE_MESH: 0,
  TRIANGLES: 1,
};

module.exports = Mesh;