phaser
Version:
A fast, free and fun HTML5 Game Framework for Desktop and Mobile web browsers from the team at Phaser Studio Inc.
1,396 lines (1,238 loc) • 48.7 kB
JavaScript
/**
* @author Richard Davey <rich@phaser.io>
* @copyright 2013-2025 Phaser Studio Inc.
* @license {@link https://opensource.org/licenses/MIT|MIT License}
*/
var Class = require('../../utils/Class');
var Components = require('../components');
var DegToRad = require('../../math/DegToRad');
var Face = require('../../geom/mesh/Face');
var GameObject = require('../GameObject');
var GenerateObjVerts = require('../../geom/mesh/GenerateObjVerts');
var GenerateVerts = require('../../geom/mesh/GenerateVerts');
var GetCalcMatrix = require('../GetCalcMatrix');
var Matrix4 = require('../../math/Matrix4');
var MeshRender = require('./MeshRender');
var RadToDeg = require('../../math/RadToDeg');
var StableSort = require('../../utils/array/StableSort');
var Vector3 = require('../../math/Vector3');
var Vertex = require('../../geom/mesh/Vertex');
/**
* @classdesc
* A Mesh Game Object.
*
* The Mesh Game Object allows you to render a group of textured vertices and manipulate
* the view of those vertices, such as rotation, translation or scaling.
*
* Support for generating mesh data from grids, model data or Wavefront OBJ Files is included.
*
* Although you can use this to render 3D objects, its primary use is for displaying more complex
* Sprites, or Sprites where you need fine-grained control over the vertex positions in order to
* achieve special effects in your games. Note that rendering still takes place using Phaser's
* orthographic camera (after being transformed via `projectionMesh`, see `setPerspective`,
* `setOrtho`, and `panZ` methods). As a result, all depth and face tests are done in an eventually
* orthographic space.
*
* The rendering process will iterate through the faces of this Mesh and render out each face
* that is considered as being in view of the camera. No depth buffer is used, and because of this,
* you should be careful not to use model data with too many vertices, or overlapping geometry,
* or you'll probably encounter z-depth fighting. The Mesh was designed to allow for more advanced
* 2D layouts, rather than displaying 3D objects, even though it can do this to a degree.
*
* In short, if you want to remake Crysis, use a 3D engine, not a Mesh. However, if you want
* to easily add some small fun 3D elements into your game, or create some special effects involving
* vertex warping, this is the right object for you. Mesh data becomes part of the WebGL batch,
* just like standard Sprites, so doesn't introduce any additional shader overhead. Because
* the Mesh just generates vertices into the WebGL batch, like any other Sprite, you can use all of
* the common Game Object components on a Mesh too, such as a custom pipeline, mask, blend mode
* or texture.
*
* Note that the Mesh object is WebGL only and does not have a Canvas counterpart.
*
* The Mesh origin is always 0.5 x 0.5 and cannot be changed.
*
* @class Mesh
* @extends Phaser.GameObjects.GameObject
* @memberof Phaser.GameObjects
* @constructor
* @webglOnly
* @since 3.0.0
*
* @extends Phaser.GameObjects.Components.AlphaSingle
* @extends Phaser.GameObjects.Components.BlendMode
* @extends Phaser.GameObjects.Components.Depth
* @extends Phaser.GameObjects.Components.Mask
* @extends Phaser.GameObjects.Components.Pipeline
* @extends Phaser.GameObjects.Components.PostPipeline
* @extends Phaser.GameObjects.Components.ScrollFactor
* @extends Phaser.GameObjects.Components.Size
* @extends Phaser.GameObjects.Components.Texture
* @extends Phaser.GameObjects.Components.Transform
* @extends Phaser.GameObjects.Components.Visible
*
* @param {Phaser.Scene} scene - The Scene to which this Game Object belongs. A Game Object can only belong to one Scene at a time.
* @param {number} [x] - The horizontal position of this Game Object in the world.
* @param {number} [y] - The vertical position of this Game Object in the world.
* @param {string|Phaser.Textures.Texture} [texture] - The key, or instance of the Texture this Game Object will use to render with, as stored in the Texture Manager.
* @param {string|number} [frame] - An optional frame from the Texture this Game Object is rendering with.
* @param {number[]} [vertices] - The vertices array. Either `xy` pairs, or `xyz` if the `containsZ` parameter is `true` (but see note).
* @param {number[]} [uvs] - The UVs pairs array.
* @param {number[]} [indicies] - Optional vertex indicies array. If you don't have one, pass `null` or an empty array.
* @param {boolean} [containsZ=false] - Does the vertices data include a `z` component? Note: If not, it will be assumed `z=0`, see method `panZ` or `setOrtho`.
* @param {number[]} [normals] - Optional vertex normals array. If you don't have one, pass `null` or an empty array.
* @param {number|number[]} [colors=0xffffff] - An array of colors, one per vertex, or a single color value applied to all vertices.
* @param {number|number[]} [alphas=1] - An array of alpha values, one per vertex, or a single alpha value applied to all vertices.
*/
var Mesh = new Class({
Extends: GameObject,
Mixins: [
Components.AlphaSingle,
Components.BlendMode,
Components.Depth,
Components.Mask,
Components.Pipeline,
Components.PostPipeline,
Components.ScrollFactor,
Components.Size,
Components.Texture,
Components.Transform,
Components.Visible,
MeshRender
],
initialize:
function Mesh (scene, x, y, texture, frame, vertices, uvs, indicies, containsZ, normals, colors, alphas)
{
if (x === undefined) { x = 0; }
if (y === undefined) { y = 0; }
if (texture === undefined) { texture = '__WHITE'; }
GameObject.call(this, scene, 'Mesh');
/**
* An array containing the Face instances belonging to this Mesh.
*
* A Face consists of 3 Vertex objects.
*
* This array is populated during calls such as `addVertices` or `addOBJ`.
*
* @name Phaser.GameObjects.Mesh#faces
* @type {Phaser.Geom.Mesh.Face[]}
* @since 3.50.0
*/
this.faces = [];
/**
* An array containing Vertex instances. One instance per vertex in this Mesh.
*
* This array is populated during calls such as `addVertex` or `addOBJ`.
*
* @name Phaser.GameObjects.Mesh#vertices
* @type {Phaser.Geom.Mesh.Vertex[]}
* @since 3.50.0
*/
this.vertices = [];
/**
* The tint fill mode.
*
* `false` = An additive tint (the default), where vertices colors are blended with the texture.
* `true` = A fill tint, where the vertex colors replace the texture, but respects texture alpha.
*
* @name Phaser.GameObjects.Mesh#tintFill
* @type {boolean}
* @default false
* @since 3.50.0
*/
this.tintFill = false;
/**
* You can optionally choose to render the vertices of this Mesh to a Graphics instance.
*
* Achieve this by setting the `debugCallback` and the `debugGraphic` properties.
*
* You can do this in a single call via the `Mesh.setDebug` method, which will use the
* built-in debug function. You can also set it to your own callback. The callback
* will be invoked _once per render_ and sent the following parameters:
*
* `debugCallback(src, meshLength, verts)`
*
* `src` is the Mesh instance being debugged.
* `meshLength` is the number of mesh vertices in total.
* `verts` is an array of the translated vertex coordinates.
*
* To disable rendering, set this property back to `null`.
*
* Please note that high vertex count Meshes will struggle to debug properly.
*
* @name Phaser.GameObjects.Mesh#debugCallback
* @type {function}
* @since 3.50.0
*/
this.debugCallback = null;
/**
* The Graphics instance that the debug vertices will be drawn to, if `setDebug` has
* been called.
*
* @name Phaser.GameObjects.Mesh#debugGraphic
* @type {Phaser.GameObjects.Graphics}
* @since 3.50.0
*/
this.debugGraphic = null;
/**
* When rendering, skip any Face that isn't counter clockwise?
*
* Enable this to hide backward-facing Faces during rendering.
*
* Disable it to render all Faces.
*
* @name Phaser.GameObjects.Mesh#hideCCW
* @type {boolean}
* @since 3.50.0
*/
this.hideCCW = true;
/**
* A Vector3 containing the 3D position of the vertices in this Mesh.
*
* Modifying the components of this property will allow you to reposition where
* the vertices are rendered within the Mesh. This happens in the `preUpdate` phase,
* where each vertex is transformed using the view and projection matrices.
*
* Changing this property will impact all vertices being rendered by this Mesh.
*
* You can also adjust the 'view' by using the `pan` methods.
*
* @name Phaser.GameObjects.Mesh#modelPosition
* @type {Phaser.Math.Vector3}
* @since 3.50.0
*/
this.modelPosition = new Vector3();
/**
* A Vector3 containing the 3D scale of the vertices in this Mesh.
*
* Modifying the components of this property will allow you to scale
* the vertices within the Mesh. This happens in the `preUpdate` phase,
* where each vertex is transformed using the view and projection matrices.
*
* Changing this property will impact all vertices being rendered by this Mesh.
*
* @name Phaser.GameObjects.Mesh#modelScale
* @type {Phaser.Math.Vector3}
* @since 3.50.0
*/
this.modelScale = new Vector3(1, 1, 1);
/**
* A Vector3 containing the 3D rotation of the vertices in this Mesh.
*
* The values should be given in radians, i.e. to rotate the vertices by 90
* degrees you can use `modelRotation.x = Phaser.Math.DegToRad(90)`.
*
* Modifying the components of this property will allow you to rotate
* the vertices within the Mesh. This happens in the `preUpdate` phase,
* where each vertex is transformed using the view and projection matrices.
*
* Changing this property will impact all vertices being rendered by this Mesh.
*
* @name Phaser.GameObjects.Mesh#modelRotation
* @type {Phaser.Math.Vector3}
* @since 3.50.0
*/
this.modelRotation = new Vector3();
/**
* An internal cache, used to compare position, rotation, scale and face data
* each frame, to avoid math calculations in `preUpdate`.
*
* Cache structure = position xyz | rotation xyz | scale xyz | face count | view | ortho
*
* @name Phaser.GameObjects.Mesh#dirtyCache
* @type {number[]}
* @private
* @since 3.50.0
*/
this.dirtyCache = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ];
/**
* The transformation matrix for this Mesh.
*
* @name Phaser.GameObjects.Mesh#transformMatrix
* @type {Phaser.Math.Matrix4}
* @since 3.50.0
*/
this.transformMatrix = new Matrix4();
/**
* The view position for this Mesh.
*
* Use the methods`panX`, `panY` and `panZ` to adjust the view.
*
* @name Phaser.GameObjects.Mesh#viewPosition
* @type {Phaser.Math.Vector3}
* @since 3.50.0
*/
this.viewPosition = new Vector3();
/**
* The view matrix for this Mesh.
*
* @name Phaser.GameObjects.Mesh#viewMatrix
* @type {Phaser.Math.Matrix4}
* @since 3.50.0
*/
this.viewMatrix = new Matrix4();
/**
* The projection matrix for this Mesh.
*
* Update it with the `setPerspective` or `setOrtho` methods.
*
* @name Phaser.GameObjects.Mesh#projectionMatrix
* @type {Phaser.Math.Matrix4}
* @since 3.50.0
*/
this.projectionMatrix = new Matrix4();
/**
* How many faces were rendered by this Mesh Game Object in the last
* draw? This is reset in the `preUpdate` method and then incremented
* each time a face is drawn. Note that in multi-camera Scenes this
* value may exceed that found in `Mesh.getFaceCount` due to
* cameras drawing the same faces more than once.
*
* @name Phaser.GameObjects.Mesh#totalRendered
* @type {number}
* @readonly
* @since 3.50.0
*/
this.totalRendered = 0;
/**
* Internal cache var for the total number of faces rendered this frame.
*
* See `totalRendered` instead for the actual value.
*
* @name Phaser.GameObjects.Mesh#totalFrame
* @type {number}
* @private
* @since 3.50.0
*/
this.totalFrame = 0;
/**
* By default, the Mesh will check to see if its model or view transform has
* changed each frame and only recalculate the vertex positions if they have.
*
* This avoids lots of additional math in the `preUpdate` step when not required.
*
* However, if you are performing per-Face or per-Vertex manipulation on this Mesh,
* such as tweening a Face, or moving it without moving the rest of the Mesh,
* then you may need to disable the dirty cache in order for the Mesh to re-render
* correctly. You can toggle this property to do that. Please note that leaving
* this set to `true` will cause the Mesh to recalculate the position of every single
* vertex in it, every single frame. So only really do this if you know you
* need it.
*
* @name Phaser.GameObjects.Mesh#ignoreDirtyCache
* @type {boolean}
* @since 3.50.0
*/
this.ignoreDirtyCache = false;
/**
* The Camera fov (field of view) in degrees.
*
* This is set automatically as part of the `Mesh.setPerspective` call, but exposed
* here for additional math.
*
* Do not modify this property directly, doing so will not change the fov. For that,
* call the respective Mesh methods.
*
* @name Phaser.GameObjects.Mesh#fov
* @type {number}
* @readonly
* @since 3.60.0
*/
this.fov;
// Set these to allow setInteractive to work
this.displayOriginX = 0;
this.displayOriginY = 0;
var renderer = scene.sys.renderer;
this.setPosition(x, y);
this.setTexture(texture, frame);
this.setSize(renderer.width, renderer.height);
this.initPipeline();
this.initPostPipeline();
this.setPerspective(renderer.width, renderer.height);
if (vertices)
{
this.addVertices(vertices, uvs, indicies, containsZ, normals, colors, alphas);
}
},
// Overrides Game Object method
addedToScene: function ()
{
this.scene.sys.updateList.add(this);
},
// Overrides Game Object method
removedFromScene: function ()
{
this.scene.sys.updateList.remove(this);
},
/**
* Translates the view position of this Mesh on the x axis by the given amount.
*
* @method Phaser.GameObjects.Mesh#panX
* @since 3.50.0
*
* @param {number} v - The amount to pan by.
*/
panX: function (v)
{
this.viewPosition.addScale(Vector3.LEFT, v);
this.dirtyCache[10] = 1;
return this;
},
/**
* Translates the view position of this Mesh on the y axis by the given amount.
*
* @method Phaser.GameObjects.Mesh#panY
* @since 3.50.0
*
* @param {number} v - The amount to pan by.
*/
panY: function (v)
{
this.viewPosition.y += Vector3.DOWN.y * v;
this.dirtyCache[10] = 1;
return this;
},
/**
* Translates the view position of this Mesh on the z axis by the given amount.
*
* As the default `panZ` value is 0, vertices with `z=0` (the default) need special
* care or else they will not display as they are "behind" the camera.
*
* Consider using `mesh.panZ(mesh.height / (2 * Math.tan(Math.PI / 16)))`,
* which will interpret vertex geometry 1:1 with pixel geometry (or see `setOrtho`).
*
* @method Phaser.GameObjects.Mesh#panZ
* @since 3.50.0
*
* @param {number} v - The amount to pan by.
*/
panZ: function (amount)
{
this.viewPosition.z += amount;
this.dirtyCache[10] = 1;
return this;
},
/**
* Builds a new perspective projection matrix from the given values.
*
* These are also the initial projection matrix and parameters for `Mesh` (see `Mesh.panZ` for more discussion).
*
* See also `setOrtho`.
*
* @method Phaser.GameObjects.Mesh#setPerspective
* @since 3.50.0
*
* @param {number} width - The width of the projection matrix. Typically the same as the Mesh and/or Renderer.
* @param {number} height - The height of the projection matrix. Typically the same as the Mesh and/or Renderer.
* @param {number} [fov=45] - The field of view, in degrees.
* @param {number} [near=0.01] - The near value of the view.
* @param {number} [far=1000] - The far value of the view.
*/
setPerspective: function (width, height, fov, near, far)
{
if (fov === undefined) { fov = 45; }
if (near === undefined) { near = 0.01; }
if (far === undefined) { far = 1000; }
this.fov = fov;
this.projectionMatrix.perspective(DegToRad(fov), width / height, near, far);
this.dirtyCache[10] = 1;
this.dirtyCache[11] = 0;
return this;
},
/**
* Builds a new orthographic projection matrix from the given values.
*
* If using this mode you will often need to set `Mesh.hideCCW` to `false` as well.
*
* By default, calling this method with no parameters will set the scaleX value to
* match the renderer's aspect ratio. If you would like to render vertex positions 1:1
* to pixel positions, consider calling as `mesh.setOrtho(mesh.width, mesh.height)`.
*
* See also `setPerspective`.
*
* @method Phaser.GameObjects.Mesh#setOrtho
* @since 3.50.0
*
* @param {number} [scaleX=1] - The default horizontal scale in relation to the Mesh / Renderer dimensions.
* @param {number} [scaleY=1] - The default vertical scale in relation to the Mesh / Renderer dimensions.
* @param {number} [near=-1000] - The near value of the view.
* @param {number} [far=1000] - The far value of the view.
*/
setOrtho: function (scaleX, scaleY, near, far)
{
if (scaleX === undefined) { scaleX = this.scene.sys.renderer.getAspectRatio(); }
if (scaleY === undefined) { scaleY = 1; }
if (near === undefined) { near = -1000; }
if (far === undefined) { far = 1000; }
this.fov = 0;
this.projectionMatrix.ortho(-scaleX, scaleX, -scaleY, scaleY, near, far);
this.dirtyCache[10] = 1;
this.dirtyCache[11] = 1;
return this;
},
/**
* Iterates and destroys all current Faces in this Mesh, then resets the
* `faces` and `vertices` arrays.
*
* @method Phaser.GameObjects.Mesh#clear
* @since 3.50.0
*
* @return {this} This Mesh Game Object.
*/
clear: function ()
{
this.faces.forEach(function (face)
{
face.destroy();
});
this.faces = [];
this.vertices = [];
return this;
},
/**
* This method will add the data from a triangulated Wavefront OBJ model file to this Mesh.
*
* The data should have been loaded via the OBJFile:
*
* ```javascript
* this.load.obj(key, url);
* ```
*
* Then use the same `key` as the first parameter to this method.
*
* Multiple Mesh Game Objects can use the same model data without impacting on each other.
*
* Make sure your 3D package has triangulated the model data prior to exporting it.
*
* You can add multiple models to a single Mesh, although they will act as one when
* moved or rotated. You can scale the model data, should it be too small, or too large, to see.
* You can also offset the vertices of the model via the `x`, `y` and `z` parameters.
*
* @method Phaser.GameObjects.Mesh#addVerticesFromObj
* @since 3.50.0
*
* @param {string} key - The key of the model data in the OBJ Cache to add to this Mesh.
* @param {number} [scale=1] - An amount to scale the model data by. Use this if the model has exported too small, or large, to see.
* @param {number} [x=0] - Translate the model x position by this amount.
* @param {number} [y=0] - Translate the model y position by this amount.
* @param {number} [z=0] - Translate the model z position by this amount.
* @param {number} [rotateX=0] - Rotate the model on the x axis by this amount, in radians.
* @param {number} [rotateY=0] - Rotate the model on the y axis by this amount, in radians.
* @param {number} [rotateZ=0] - Rotate the model on the z axis by this amount, in radians.
* @param {boolean} [zIsUp=true] - Is the z axis up (true), or is y axis up (false)?
*
* @return {this} This Mesh Game Object.
*/
addVerticesFromObj: function (key, scale, x, y, z, rotateX, rotateY, rotateZ, zIsUp)
{
var data = this.scene.sys.cache.obj.get(key);
var parsedData;
if (data)
{
parsedData = GenerateObjVerts(data, this, scale, x, y, z, rotateX, rotateY, rotateZ, zIsUp);
}
if (!parsedData || parsedData.verts.length === 0)
{
console.warn('Mesh.addVerticesFromObj data empty:', key);
}
return this;
},
/**
* Compare the depth of two Faces.
*
* @method Phaser.GameObjects.Mesh#sortByDepth
* @since 3.50.0
*
* @param {Phaser.Geom.Mesh.Face} faceA - The first Face.
* @param {Phaser.Geom.Mesh.Face} faceB - The second Face.
*
* @return {number} The difference between the depths of each Face.
*/
sortByDepth: function (faceA, faceB)
{
return faceA.depth - faceB.depth;
},
/**
* Runs a depth sort across all Faces in this Mesh, comparing their averaged depth.
*
* This is called automatically if you use any of the `rotate` methods, but you can
* also invoke it to sort the Faces should you manually position them.
*
* @method Phaser.GameObjects.Mesh#depthSort
* @since 3.50.0
*
* @return {this} This Mesh Game Object.
*/
depthSort: function ()
{
StableSort(this.faces, this.sortByDepth);
return this;
},
/**
* Adds a new Vertex into the vertices array of this Mesh.
*
* Just adding a vertex isn't enough to render it. You need to also
* make it part of a Face, with 3 Vertex instances per Face.
*
* @method Phaser.GameObjects.Mesh#addVertex
* @since 3.50.0
*
* @param {number} x - The x position of the vertex.
* @param {number} y - The y position of the vertex.
* @param {number} z - The z position of the vertex.
* @param {number} u - The UV u coordinate of the vertex.
* @param {number} v - The UV v coordinate of the vertex.
* @param {number} [color=0xffffff] - The color value of the vertex.
* @param {number} [alpha=1] - The alpha value of the vertex.
*
* @return {this} This Mesh Game Object.
*/
addVertex: function (x, y, z, u, v, color, alpha)
{
var vert = new Vertex(x, y, z, u, v, color, alpha);
this.vertices.push(vert);
return vert;
},
/**
* Adds a new Face into the faces array of this Mesh.
*
* A Face consists of references to 3 Vertex instances, which must be provided.
*
* @method Phaser.GameObjects.Mesh#addFace
* @since 3.50.0
*
* @param {Phaser.Geom.Mesh.Vertex} vertex1 - The first vertex of the Face.
* @param {Phaser.Geom.Mesh.Vertex} vertex2 - The second vertex of the Face.
* @param {Phaser.Geom.Mesh.Vertex} vertex3 - The third vertex of the Face.
*
* @return {this} This Mesh Game Object.
*/
addFace: function (vertex1, vertex2, vertex3)
{
var face = new Face(vertex1, vertex2, vertex3);
this.faces.push(face);
this.dirtyCache[9] = -1;
return face;
},
/**
* Adds new vertices to this Mesh by parsing the given data.
*
* This method will take vertex data in one of two formats, based on the `containsZ` parameter.
*
* If your vertex data are `x`, `y` pairs, then `containsZ` should be `false` (this is the default, and will result in `z=0` for each vertex).
*
* If your vertex data is groups of `x`, `y` and `z` values, then the `containsZ` parameter must be true.
*
* The `uvs` parameter is a numeric array consisting of `u` and `v` pairs.
*
* The `normals` parameter is a numeric array consisting of `x`, `y` vertex normal values and, if `containsZ` is true, `z` values as well.
*
* The `indicies` parameter is an optional array that, if given, is an indexed list of vertices to be added.
*
* The `colors` parameter is an optional array, or single value, that if given sets the color of each vertex created.
*
* The `alphas` parameter is an optional array, or single value, that if given sets the alpha of each vertex created.
*
* When providing indexed data it is assumed that _all_ of the arrays are indexed, not just the vertices.
*
* The following example will create a 256 x 256 sized quad using an index array:
*
* ```javascript
* let mesh = new Mesh(this); // Assuming `this` is a scene!
* const vertices = [
* -128, 128,
* 128, 128,
* -128, -128,
* 128, -128
* ];
*
* const uvs = [
* 0, 1,
* 1, 1,
* 0, 0,
* 1, 0
* ];
*
* const indices = [ 0, 2, 1, 2, 3, 1 ];
*
* mesh.addVertices(vertices, uvs, indicies);
* // Note: Otherwise the added points will be "behind" the camera! This value will project vertex `x` & `y` values 1:1 to pixel values.
* mesh.hideCCW = false;
* mesh.setOrtho(mesh.width, mesh.height);
* ```
*
* If the data is not indexed, it's assumed that the arrays all contain sequential data.
*
* @method Phaser.GameObjects.Mesh#addVertices
* @since 3.50.0
*
* @param {number[]} vertices - The vertices array. Either `xy` pairs, or `xyz` if the `containsZ` parameter is `true`.
* @param {number[]} uvs - The UVs pairs array.
* @param {number[]} [indicies] - Optional vertex indicies array. If you don't have one, pass `null` or an empty array.
* @param {boolean} [containsZ=false] - Does the vertices data include a `z` component? If not, it will be assumed `z=0`, see methods `panZ` or `setOrtho`.
* @param {number[]} [normals] - Optional vertex normals array. If you don't have one, pass `null` or an empty array.
* @param {number|number[]} [colors=0xffffff] - An array of colors, one per vertex, or a single color value applied to all vertices.
* @param {number|number[]} [alphas=1] - An array of alpha values, one per vertex, or a single alpha value applied to all vertices.
*
* @return {this} This Mesh Game Object.
*/
addVertices: function (vertices, uvs, indicies, containsZ, normals, colors, alphas)
{
var result = GenerateVerts(vertices, uvs, indicies, containsZ, normals, colors, alphas);
if (result)
{
this.faces = this.faces.concat(result.faces);
this.vertices = this.vertices.concat(result.vertices);
}
else
{
console.warn('Mesh.addVertices data empty or invalid');
}
this.dirtyCache[9] = -1;
return this;
},
/**
* Returns the total number of Faces in this Mesh Game Object.
*
* @method Phaser.GameObjects.Mesh#getFaceCount
* @since 3.50.0
*
* @return {number} The number of Faces in this Mesh Game Object.
*/
getFaceCount: function ()
{
return this.faces.length;
},
/**
* Returns the total number of Vertices in this Mesh Game Object.
*
* @method Phaser.GameObjects.Mesh#getVertexCount
* @since 3.50.0
*
* @return {number} The number of Vertices in this Mesh Game Object.
*/
getVertexCount: function ()
{
return this.vertices.length;
},
/**
* Returns the Face at the given index in this Mesh Game Object.
*
* @method Phaser.GameObjects.Mesh#getFace
* @since 3.50.0
*
* @param {number} index - The index of the Face to get.
*
* @return {Phaser.Geom.Mesh.Face} The Face at the given index, or `undefined` if index out of range.
*/
getFace: function (index)
{
return this.faces[index];
},
/**
* Tests to see if _any_ face in this Mesh intersects with the given coordinates.
*
* The given position is translated through the matrix of this Mesh and the given Camera,
* before being compared against the vertices.
*
* @method Phaser.GameObjects.Mesh#hasFaceAt
* @since 3.60.0
*
* @param {number} x - The x position to check against.
* @param {number} y - The y position to check against.
* @param {Phaser.Cameras.Scene2D.Camera} [camera] - The camera to pass the coordinates through. If not give, the default Scene Camera is used.
*
* @return {boolean} Returns `true` if _any_ face of this Mesh intersects with the given coordinate, otherwise `false`.
*/
hasFaceAt: function (x, y, camera)
{
if (camera === undefined) { camera = this.scene.sys.cameras.main; }
var calcMatrix = GetCalcMatrix(this, camera).calc;
var faces = this.faces;
for (var i = 0; i < faces.length; i++)
{
var face = faces[i];
if (face.contains(x, y, calcMatrix))
{
return true;
}
}
return false;
},
/**
* Return an array of Face objects from this Mesh that intersect with the given coordinates.
*
* The given position is translated through the matrix of this Mesh and the given Camera,
* before being compared against the vertices.
*
* If more than one Face intersects, they will all be returned in the array, but the array will
* be depth sorted first, so the first element will always be that closest to the camera.
*
* @method Phaser.GameObjects.Mesh#getFaceAt
* @since 3.50.0
*
* @param {number} x - The x position to check against.
* @param {number} y - The y position to check against.
* @param {Phaser.Cameras.Scene2D.Camera} [camera] - The camera to pass the coordinates through. If not give, the default Scene Camera is used.
*
* @return {Phaser.Geom.Mesh.Face[]} An array of Face objects that intersect with the given point, ordered by depth.
*/
getFaceAt: function (x, y, camera)
{
if (camera === undefined) { camera = this.scene.sys.cameras.main; }
var calcMatrix = GetCalcMatrix(this, camera).calc;
var faces = this.faces;
var results = [];
for (var i = 0; i < faces.length; i++)
{
var face = faces[i];
if (face.contains(x, y, calcMatrix))
{
results.push(face);
}
}
return StableSort(results, this.sortByDepth);
},
/**
* This method enables rendering of the Mesh vertices to the given Graphics instance.
*
* If you enable this feature, you **must** call `Graphics.clear()` in your Scene `update`,
* otherwise the Graphics instance you provide to debug will fill-up with draw calls,
* eventually crashing the browser. This is not done automatically to allow you to debug
* draw multiple Mesh objects to a single Graphics instance.
*
* The Mesh class has a built-in debug rendering callback `Mesh.renderDebug`, however
* you can also provide your own callback to be used instead. Do this by setting the `callback` parameter.
*
* The callback is invoked _once per render_ and sent the following parameters:
*
* `callback(src, faces)`
*
* `src` is the Mesh instance being debugged.
* `faces` is an array of the Faces that were rendered.
*
* You can get the final drawn vertex position from a Face object like this:
*
* ```javascript
* let face = faces[i];
*
* let x0 = face.vertex1.tx;
* let y0 = face.vertex1.ty;
* let x1 = face.vertex2.tx;
* let y1 = face.vertex2.ty;
* let x2 = face.vertex3.tx;
* let y2 = face.vertex3.ty;
*
* graphic.strokeTriangle(x0, y0, x1, y1, x2, y2);
* ```
*
* If using your own callback you do not have to provide a Graphics instance to this method.
*
* To disable debug rendering, to either your own callback or the built-in one, call this method
* with no arguments.
*
* @method Phaser.GameObjects.Mesh#setDebug
* @since 3.50.0
*
* @param {Phaser.GameObjects.Graphics} [graphic] - The Graphic instance to render to if using the built-in callback.
* @param {function} [callback] - The callback to invoke during debug render. Leave as undefined to use the built-in callback.
*
* @return {this} This Game Object instance.
*/
setDebug: function (graphic, callback)
{
this.debugGraphic = graphic;
if (!graphic && !callback)
{
this.debugCallback = null;
}
else if (!callback)
{
this.debugCallback = this.renderDebug;
}
else
{
this.debugCallback = callback;
}
return this;
},
/**
* Checks if the transformation data in this mesh is dirty.
*
* This is used internally by the `preUpdate` step to determine if the vertices should
* be recalculated or not.
*
* @method Phaser.GameObjects.Mesh#isDirty
* @since 3.50.0
*
* @return {boolean} Returns `true` if the data of this mesh is dirty, otherwise `false`.
*/
isDirty: function ()
{
var position = this.modelPosition;
var rotation = this.modelRotation;
var scale = this.modelScale;
var dirtyCache = this.dirtyCache;
var px = position.x;
var py = position.y;
var pz = position.z;
var rx = rotation.x;
var ry = rotation.y;
var rz = rotation.z;
var sx = scale.x;
var sy = scale.y;
var sz = scale.z;
var faces = this.getFaceCount();
var pxCached = dirtyCache[0];
var pyCached = dirtyCache[1];
var pzCached = dirtyCache[2];
var rxCached = dirtyCache[3];
var ryCached = dirtyCache[4];
var rzCached = dirtyCache[5];
var sxCached = dirtyCache[6];
var syCached = dirtyCache[7];
var szCached = dirtyCache[8];
var fCached = dirtyCache[9];
dirtyCache[0] = px;
dirtyCache[1] = py;
dirtyCache[2] = pz;
dirtyCache[3] = rx;
dirtyCache[4] = ry;
dirtyCache[5] = rz;
dirtyCache[6] = sx;
dirtyCache[7] = sy;
dirtyCache[8] = sz;
dirtyCache[9] = faces;
return (
pxCached !== px || pyCached !== py || pzCached !== pz ||
rxCached !== rx || ryCached !== ry || rzCached !== rz ||
sxCached !== sx || syCached !== sy || szCached !== sz ||
fCached !== faces
);
},
/**
* The Mesh update loop. The following takes place in this method:
*
* First, the `totalRendered` and `totalFrame` properties are set.
*
* If the view matrix of this Mesh isn't dirty, and the model position, rotate or scale properties are
* all clean, then the method returns at this point.
*
* Otherwise, if the viewPosition is dirty (i.e. from calling a method like `panZ`), then it will
* refresh the viewMatrix.
*
* After this, a new transformMatrix is built and it then iterates through all Faces in this
* Mesh, calling `transformCoordinatesLocal` on all of them. Internally, this updates every
* vertex, calculating its new transformed position, based on the new transform matrix.
*
* Finally, the faces are depth sorted.
*
* @method Phaser.GameObjects.Mesh#preUpdate
* @protected
* @since 3.50.0
*
* @param {number} time - The current timestamp.
* @param {number} delta - The delta time, in ms, elapsed since the last frame.
*/
preUpdate: function ()
{
this.totalRendered = this.totalFrame;
this.totalFrame = 0;
var dirty = this.dirtyCache;
if (!this.ignoreDirtyCache && !dirty[10] && !this.isDirty())
{
// If neither the view or the mesh is dirty we can bail out and save lots of math
return;
}
var width = this.width;
var height = this.height;
var viewMatrix = this.viewMatrix;
var viewPosition = this.viewPosition;
if (dirty[10])
{
viewMatrix.identity();
viewMatrix.translate(viewPosition);
viewMatrix.invert();
dirty[10] = 0;
}
var transformMatrix = this.transformMatrix;
transformMatrix.setWorldMatrix(
this.modelRotation,
this.modelPosition,
this.modelScale,
this.viewMatrix,
this.projectionMatrix
);
var z = viewPosition.z;
var faces = this.faces;
for (var i = 0; i < faces.length; i++)
{
faces[i].transformCoordinatesLocal(transformMatrix, width, height, z);
}
this.depthSort();
},
/**
* The built-in Mesh debug rendering method.
*
* See `Mesh.setDebug` for more details.
*
* @method Phaser.GameObjects.Mesh#renderDebug
* @since 3.50.0
*
* @param {Phaser.GameObjects.Mesh} src - The Mesh object being rendered.
* @param {Phaser.Geom.Mesh.Face[]} faces - An array of Faces.
*/
renderDebug: function (src, faces)
{
var graphic = src.debugGraphic;
for (var i = 0; i < faces.length; i++)
{
var face = faces[i];
var x0 = face.vertex1.tx;
var y0 = face.vertex1.ty;
var x1 = face.vertex2.tx;
var y1 = face.vertex2.ty;
var x2 = face.vertex3.tx;
var y2 = face.vertex3.ty;
graphic.strokeTriangle(x0, y0, x1, y1, x2, y2);
}
},
/**
* Handles the pre-destroy step for the Mesh, which removes the vertices and debug callbacks.
*
* @method Phaser.GameObjects.Mesh#preDestroy
* @private
* @since 3.50.0
*/
preDestroy: function ()
{
this.clear();
this.debugCallback = null;
this.debugGraphic = null;
},
/**
* Clears all tint values associated with this Game Object.
*
* Immediately sets the color values back to 0xffffff on all vertices,
* which results in no visible change to the texture.
*
* @method Phaser.GameObjects.Mesh#clearTint
* @webglOnly
* @since 3.60.0
*
* @return {this} This Game Object instance.
*/
clearTint: function ()
{
return this.setTint();
},
/**
* Pass this Mesh Game Object to the Input Manager to enable it for Input.
*
* Unlike other Game Objects, the Mesh Game Object uses its own special hit area callback, which you cannot override.
*
* @example
* mesh.setInteractive();
*
* @example
* mesh.setInteractive({ useHandCursor: true });
*
* @method Phaser.GameObjects.Mesh#setInteractive
* @since 3.60.0
*
* @param {(Phaser.Types.Input.InputConfiguration)} [config] - An input configuration object but it will ignore hitArea, hitAreaCallback and pixelPerfect with associated alphaTolerance properties.
*
* @return {this} This GameObject.
*/
setInteractive: function (config)
{
if (config === undefined) { config = {}; }
var hitAreaCallback = function (area, x, y)
{
var faces = this.faces;
for (var i = 0; i < faces.length; i++)
{
var face = faces[i];
// Don't pass a calcMatrix, as the x/y are already transformed
if (face.contains(x, y))
{
return true;
}
}
return false;
}.bind(this);
this.scene.sys.input.enable(this, config, hitAreaCallback);
return this;
},
/**
* Sets an additive tint on all vertices of this Mesh Game Object.
*
* The tint works by taking the pixel color values from the Game Objects texture, and then
* multiplying it by the color value of the tint.
*
* To modify the tint color once set, either call this method again with new values or use the
* `tint` property to set all colors at once.
*
* To remove a tint call `clearTint`.
*
* @method Phaser.GameObjects.Mesh#setTint
* @webglOnly
* @since 3.60.0
*
* @param {number} [tint=0xffffff] - The tint being applied to all vertices of this Mesh Game Object.
*
* @return {this} This Game Object instance.
*/
setTint: function (tint)
{
if (tint === undefined) { tint = 0xffffff; }
var vertices = this.vertices;
for (var i = 0; i < vertices.length; i++)
{
vertices[i].color = tint;
}
return this;
},
/**
* Scrolls the UV texture coordinates of all faces in this Mesh by
* adding the given x/y amounts to them.
*
* If you only wish to scroll one coordinate, pass a value of zero
* to the other.
*
* Use small values for scrolling. UVs are set from the range 0
* to 1, so you should increment (or decrement) them by suitably
* small values, such as 0.01.
*
* Due to a limitation in WebGL1 you can only UV scroll textures
* that are a power-of-two in size. Scrolling NPOT textures will
* work but will result in clamping the pixels to the edges.
*
* Note that if this Mesh is using a _frame_ from a texture atlas
* then you will be unable to UV scroll its texture.
*
* @method Phaser.GameObjects.Mesh#uvScroll
* @webglOnly
* @since 3.60.0
*
* @param {number} x - The amount to horizontally shift the UV coordinates by.
* @param {number} y - The amount to vertically shift the UV coordinates by.
*
* @return {this} This Game Object instance.
*/
uvScroll: function (x, y)
{
var faces = this.faces;
for (var i = 0; i < faces.length; i++)
{
faces[i].scrollUV(x, y);
}
return this;
},
/**
* Scales the UV texture coordinates of all faces in this Mesh by
* the exact given amounts.
*
* If you only wish to scale one coordinate, pass a value of one
* to the other.
*
* Due to a limitation in WebGL1 you can only UV scale textures
* that are a power-of-two in size. Scaling NPOT textures will
* work but will result in clamping the pixels to the edges if
* you scale beyond a value of 1. Scaling below 1 will work
* regardless of texture size.
*
* Note that if this Mesh is using a _frame_ from a texture atlas
* then you will be unable to UV scale its texture.
*
* @method Phaser.GameObjects.Mesh#uvScale
* @webglOnly
* @since 3.60.0
*
* @param {number} x - The amount to horizontally scale the UV coordinates by.
* @param {number} y - The amount to vertically scale the UV coordinates by.
*
* @return {this} This Game Object instance.
*/
uvScale: function (x, y)
{
var faces = this.faces;
for (var i = 0; i < faces.length; i++)
{
faces[i].scaleUV(x, y);
}
return this;
},
/**
* The tint value being applied to the whole of the Game Object.
* This property is a setter-only.
*
* @method Phaser.GameObjects.Mesh#tint
* @type {number}
* @webglOnly
* @since 3.60.0
*/
tint: {
set: function (value)
{
this.setTint(value);
}
},
/**
* The x rotation of the Model in 3D space, as specified in degrees.
*
* If you need the value in radians use the `modelRotation.x` property directly.
*
* @method Phaser.GameObjects.Mesh#rotateX
* @type {number}
* @since 3.60.0
*/
rotateX: {
get: function ()
{
return RadToDeg(this.modelRotation.x);
},
set: function (value)
{
this.modelRotation.x = DegToRad(value);
}
},
/**
* The y rotation of the Model in 3D space, as specified in degrees.
*
* If you need the value in radians use the `modelRotation.y` property directly.
*
* @method Phaser.GameObjects.Mesh#rotateY
* @type {number}
* @since 3.60.0
*/
rotateY: {
get: function ()
{
return RadToDeg(this.modelRotation.y);
},
set: function (value)
{
this.modelRotation.y = DegToRad(value);
}
},
/**
* The z rotation of the Model in 3D space, as specified in degrees.
*
* If you need the value in radians use the `modelRotation.z` property directly.
*
* @method Phaser.GameObjects.Mesh#rotateZ
* @type {number}
* @since 3.60.0
*/
rotateZ: {
get: function ()
{
return RadToDeg(this.modelRotation.z);
},
set: function (value)
{
this.modelRotation.z = DegToRad(value);
}
}
});
module.exports = Mesh;