/** * @author Richard Davey * @copyright 2019 Photon Storm Ltd. * @license {@link https://opensource.org/licenses/MIT|MIT License} */ var Class = require('../utils/Class'); var Clamp = require('../math/Clamp'); var Extend = require('../utils/object/Extend'); /** * @classdesc * A Frame is a section of a Texture. * * @class Frame * @memberof Phaser.Textures * @constructor * @since 3.0.0 * * @param {Phaser.Textures.Texture} texture - The Texture this Frame is a part of. * @param {(integer|string)} name - The name of this Frame. The name is unique within the Texture. * @param {integer} sourceIndex - The index of the TextureSource that this Frame is a part of. * @param {number} x - The x coordinate of the top-left of this Frame. * @param {number} y - The y coordinate of the top-left of this Frame. * @param {number} width - The width of this Frame. * @param {number} height - The height of this Frame. */ var Frame = new Class({ initialize: function Frame (texture, name, sourceIndex, x, y, width, height) { /** * The Texture this Frame is a part of. * * @name Phaser.Textures.Frame#texture * @type {Phaser.Textures.Texture} * @since 3.0.0 */ this.texture = texture; /** * The name of this Frame. * The name is unique within the Texture. * * @name Phaser.Textures.Frame#name * @type {string} * @since 3.0.0 */ this.name = name; /** * The TextureSource this Frame is part of. * * @name Phaser.Textures.Frame#source * @type {Phaser.Textures.TextureSource} * @since 3.0.0 */ this.source = texture.source[sourceIndex]; /** * The index of the TextureSource in the Texture sources array. * * @name Phaser.Textures.Frame#sourceIndex * @type {integer} * @since 3.0.0 */ this.sourceIndex = sourceIndex; /** * A reference to the Texture Source WebGL Texture that this Frame is using. * * @name Phaser.Textures.Frame#glTexture * @type {?WebGLTexture} * @default null * @since 3.11.0 */ this.glTexture = this.source.glTexture; /** * X position within the source image to cut from. * * @name Phaser.Textures.Frame#cutX * @type {integer} * @since 3.0.0 */ this.cutX; /** * Y position within the source image to cut from. * * @name Phaser.Textures.Frame#cutY * @type {integer} * @since 3.0.0 */ this.cutY; /** * The width of the area in the source image to cut. * * @name Phaser.Textures.Frame#cutWidth * @type {integer} * @since 3.0.0 */ this.cutWidth; /** * The height of the area in the source image to cut. * * @name Phaser.Textures.Frame#cutHeight * @type {integer} * @since 3.0.0 */ this.cutHeight; /** * The X rendering offset of this Frame, taking trim into account. * * @name Phaser.Textures.Frame#x * @type {integer} * @default 0 * @since 3.0.0 */ this.x = 0; /** * The Y rendering offset of this Frame, taking trim into account. * * @name Phaser.Textures.Frame#y * @type {integer} * @default 0 * @since 3.0.0 */ this.y = 0; /** * The rendering width of this Frame, taking trim into account. * * @name Phaser.Textures.Frame#width * @type {integer} * @since 3.0.0 */ this.width; /** * The rendering height of this Frame, taking trim into account. * * @name Phaser.Textures.Frame#height * @type {integer} * @since 3.0.0 */ this.height; /** * Half the width, floored. * Precalculated for the renderer. * * @name Phaser.Textures.Frame#halfWidth * @type {integer} * @since 3.0.0 */ this.halfWidth; /** * Half the height, floored. * Precalculated for the renderer. * * @name Phaser.Textures.Frame#halfHeight * @type {integer} * @since 3.0.0 */ this.halfHeight; /** * The x center of this frame, floored. * * @name Phaser.Textures.Frame#centerX * @type {integer} * @since 3.0.0 */ this.centerX; /** * The y center of this frame, floored. * * @name Phaser.Textures.Frame#centerY * @type {integer} * @since 3.0.0 */ this.centerY; /** * The horizontal pivot point of this Frame. * * @name Phaser.Textures.Frame#pivotX * @type {number} * @default 0 * @since 3.0.0 */ this.pivotX = 0; /** * The vertical pivot point of this Frame. * * @name Phaser.Textures.Frame#pivotY * @type {number} * @default 0 * @since 3.0.0 */ this.pivotY = 0; /** * Does this Frame have a custom pivot point? * * @name Phaser.Textures.Frame#customPivot * @type {boolean} * @default false * @since 3.0.0 */ this.customPivot = false; /** * **CURRENTLY UNSUPPORTED** * * Is this frame is rotated or not in the Texture? * Rotation allows you to use rotated frames in texture atlas packing. * It has nothing to do with Sprite rotation. * * @name Phaser.Textures.Frame#rotated * @type {boolean} * @default false * @since 3.0.0 */ this.rotated = false; /** * Over-rides the Renderer setting. * -1 = use Renderer Setting * 0 = No rounding * 1 = Round * * @name Phaser.Textures.Frame#autoRound * @type {integer} * @default -1 * @since 3.0.0 */ this.autoRound = -1; /** * Any Frame specific custom data can be stored here. * * @name Phaser.Textures.Frame#customData * @type {object} * @since 3.0.0 */ this.customData = {}; /** * WebGL UV u0 value. * * @name Phaser.Textures.Frame#u0 * @type {number} * @default 0 * @since 3.11.0 */ this.u0 = 0; /** * WebGL UV v0 value. * * @name Phaser.Textures.Frame#v0 * @type {number} * @default 0 * @since 3.11.0 */ this.v0 = 0; /** * WebGL UV u1 value. * * @name Phaser.Textures.Frame#u1 * @type {number} * @default 0 * @since 3.11.0 */ this.u1 = 0; /** * WebGL UV v1 value. * * @name Phaser.Textures.Frame#v1 * @type {number} * @default 0 * @since 3.11.0 */ this.v1 = 0; /** * The un-modified source frame, trim and UV data. * * @name Phaser.Textures.Frame#data * @type {object} * @private * @since 3.0.0 */ this.data = { cut: { x: 0, y: 0, w: 0, h: 0, r: 0, b: 0 }, trim: false, sourceSize: { w: 0, h: 0 }, spriteSourceSize: { x: 0, y: 0, w: 0, h: 0, r: 0, b: 0 }, radius: 0, drawImage: { x: 0, y: 0, width: 0, height: 0 } }; this.setSize(width, height, x, y); }, /** * Sets the width, height, x and y of this Frame. * * This is called automatically by the constructor * and should rarely be changed on-the-fly. * * @method Phaser.Textures.Frame#setSize * @since 3.7.0 * * @param {integer} width - The width of the frame before being trimmed. * @param {integer} height - The height of the frame before being trimmed. * @param {integer} [x=0] - The x coordinate of the top-left of this Frame. * @param {integer} [y=0] - The y coordinate of the top-left of this Frame. * * @return {Phaser.Textures.Frame} This Frame object. */ setSize: function (width, height, x, y) { if (x === undefined) { x = 0; } if (y === undefined) { y = 0; } this.cutX = x; this.cutY = y; this.cutWidth = width; this.cutHeight = height; this.width = width; this.height = height; this.halfWidth = Math.floor(width * 0.5); this.halfHeight = Math.floor(height * 0.5); this.centerX = Math.floor(width / 2); this.centerY = Math.floor(height / 2); var data = this.data; var cut = data.cut; cut.x = x; cut.y = y; cut.w = width; cut.h = height; cut.r = x + width; cut.b = y + height; data.sourceSize.w = width; data.sourceSize.h = height; data.spriteSourceSize.w = width; data.spriteSourceSize.h = height; data.radius = 0.5 * Math.sqrt(width * width + height * height); var drawImage = data.drawImage; drawImage.x = x; drawImage.y = y; drawImage.width = width; drawImage.height = height; return this.updateUVs(); }, /** * If the frame was trimmed when added to the Texture Atlas, this records the trim and source data. * * @method Phaser.Textures.Frame#setTrim * @since 3.0.0 * * @param {number} actualWidth - The width of the frame before being trimmed. * @param {number} actualHeight - The height of the frame before being trimmed. * @param {number} destX - The destination X position of the trimmed frame for display. * @param {number} destY - The destination Y position of the trimmed frame for display. * @param {number} destWidth - The destination width of the trimmed frame for display. * @param {number} destHeight - The destination height of the trimmed frame for display. * * @return {Phaser.Textures.Frame} This Frame object. */ setTrim: function (actualWidth, actualHeight, destX, destY, destWidth, destHeight) { var data = this.data; var ss = data.spriteSourceSize; // Store actual values data.trim = true; data.sourceSize.w = actualWidth; data.sourceSize.h = actualHeight; ss.x = destX; ss.y = destY; ss.w = destWidth; ss.h = destHeight; ss.r = destX + destWidth; ss.b = destY + destHeight; // Adjust properties this.x = destX; this.y = destY; this.width = destWidth; this.height = destHeight; this.halfWidth = destWidth * 0.5; this.halfHeight = destHeight * 0.5; this.centerX = Math.floor(destWidth / 2); this.centerY = Math.floor(destHeight / 2); return this.updateUVs(); }, /** * Takes a crop data object and, based on the rectangular region given, calculates the * required UV coordinates in order to crop this Frame for WebGL and Canvas rendering. * * This is called directly by the Game Object Texture Components `setCrop` method. * Please use that method to crop a Game Object. * * @method Phaser.Textures.Frame#setCropUVs * @since 3.11.0 * * @param {object} crop - The crop data object. This is the `GameObject._crop` property. * @param {number} x - The x coordinate to start the crop from. Cannot be negative or exceed the Frame width. * @param {number} y - The y coordinate to start the crop from. Cannot be negative or exceed the Frame height. * @param {number} width - The width of the crop rectangle. Cannot exceed the Frame width. * @param {number} height - The height of the crop rectangle. Cannot exceed the Frame height. * @param {boolean} flipX - Does the parent Game Object have flipX set? * @param {boolean} flipY - Does the parent Game Object have flipY set? * * @return {object} The updated crop data object. */ setCropUVs: function (crop, x, y, width, height, flipX, flipY) { // Clamp the input values var cx = this.cutX; var cy = this.cutY; var cw = this.cutWidth; var ch = this.cutHeight; var rw = this.realWidth; var rh = this.realHeight; x = Clamp(x, 0, rw); y = Clamp(y, 0, rh); width = Clamp(width, 0, rw - x); height = Clamp(height, 0, rh - y); var ox = cx + x; var oy = cy + y; var ow = width; var oh = height; var data = this.data; if (data.trim) { var ss = data.spriteSourceSize; // Need to check for intersection between the cut area and the crop area // If there is none, we set UV to be empty, otherwise set it to be the intersection area width = Clamp(width, 0, cw - x); height = Clamp(height, 0, ch - y); var cropRight = x + width; var cropBottom = y + height; var intersects = !(ss.r < x || ss.b < y || ss.x > cropRight || ss.y > cropBottom); if (intersects) { var ix = Math.max(ss.x, x); var iy = Math.max(ss.y, y); var iw = Math.min(ss.r, cropRight) - ix; var ih = Math.min(ss.b, cropBottom) - iy; ow = iw; oh = ih; if (flipX) { ox = cx + (cw - (ix - ss.x) - iw); } else { ox = cx + (ix - ss.x); } if (flipY) { oy = cy + (ch - (iy - ss.y) - ih); } else { oy = cy + (iy - ss.y); } x = ix; y = iy; width = iw; height = ih; } else { ox = 0; oy = 0; ow = 0; oh = 0; } } else { if (flipX) { ox = cx + (cw - x - width); } if (flipY) { oy = cy + (ch - y - height); } } var tw = this.source.width; var th = this.source.height; // Map the given coordinates into UV space, clamping to the 0-1 range. crop.u0 = Math.max(0, ox / tw); crop.v0 = Math.max(0, oy / th); crop.u1 = Math.min(1, (ox + ow) / tw); crop.v1 = Math.min(1, (oy + oh) / th); crop.x = x; crop.y = y; crop.cx = ox; crop.cy = oy; crop.cw = ow; crop.ch = oh; crop.width = width; crop.height = height; crop.flipX = flipX; crop.flipY = flipY; return crop; }, /** * Takes a crop data object and recalculates the UVs based on the dimensions inside the crop object. * Called automatically by `setFrame`. * * @method Phaser.Textures.Frame#updateCropUVs * @since 3.11.0 * * @param {object} crop - The crop data object. This is the `GameObject._crop` property. * @param {boolean} flipX - Does the parent Game Object have flipX set? * @param {boolean} flipY - Does the parent Game Object have flipY set? * * @return {object} The updated crop data object. */ updateCropUVs: function (crop, flipX, flipY) { return this.setCropUVs(crop, crop.x, crop.y, crop.width, crop.height, flipX, flipY); }, /** * Updates the internal WebGL UV cache and the drawImage cache. * * @method Phaser.Textures.Frame#updateUVs * @since 3.0.0 * * @return {Phaser.Textures.Frame} This Frame object. */ updateUVs: function () { var cx = this.cutX; var cy = this.cutY; var cw = this.cutWidth; var ch = this.cutHeight; // Canvas data var cd = this.data.drawImage; cd.width = cw; cd.height = ch; // WebGL data var tw = this.source.width; var th = this.source.height; this.u0 = cx / tw; this.v0 = cy / th; this.u1 = (cx + cw) / tw; this.v1 = (cy + ch) / th; return this; }, /** * Updates the internal WebGL UV cache. * * @method Phaser.Textures.Frame#updateUVsInverted * @since 3.0.0 * * @return {Phaser.Textures.Frame} This Frame object. */ updateUVsInverted: function () { var tw = this.source.width; var th = this.source.height; this.u0 = (this.cutX + this.cutHeight) / tw; this.v0 = this.cutY / th; this.u1 = this.cutX / tw; this.v1 = (this.cutY + this.cutWidth) / th; return this; }, /** * Clones this Frame into a new Frame object. * * @method Phaser.Textures.Frame#clone * @since 3.0.0 * * @return {Phaser.Textures.Frame} A clone of this Frame. */ clone: function () { var clone = new Frame(this.texture, this.name, this.sourceIndex); clone.cutX = this.cutX; clone.cutY = this.cutY; clone.cutWidth = this.cutWidth; clone.cutHeight = this.cutHeight; clone.x = this.x; clone.y = this.y; clone.width = this.width; clone.height = this.height; clone.halfWidth = this.halfWidth; clone.halfHeight = this.halfHeight; clone.centerX = this.centerX; clone.centerY = this.centerY; clone.rotated = this.rotated; clone.data = Extend(true, clone.data, this.data); clone.updateUVs(); return clone; }, /** * Destroys this Frame by nulling its reference to the parent Texture and and data objects. * * @method Phaser.Textures.Frame#destroy * @since 3.0.0 */ destroy: function () { this.source = null; this.texture = null; this.glTexture = null; this.customData = null; this.data = null; }, /** * The width of the Frame in its un-trimmed, un-padded state, as prepared in the art package, * before being packed. * * @name Phaser.Textures.Frame#realWidth * @type {number} * @readonly * @since 3.0.0 */ realWidth: { get: function () { return this.data.sourceSize.w; } }, /** * The height of the Frame in its un-trimmed, un-padded state, as prepared in the art package, * before being packed. * * @name Phaser.Textures.Frame#realHeight * @type {number} * @readonly * @since 3.0.0 */ realHeight: { get: function () { return this.data.sourceSize.h; } }, /** * The radius of the Frame (derived from sqrt(w * w + h * h) / 2) * * @name Phaser.Textures.Frame#radius * @type {number} * @readonly * @since 3.0.0 */ radius: { get: function () { return this.data.radius; } }, /** * Is the Frame trimmed or not? * * @name Phaser.Textures.Frame#trimmed * @type {boolean} * @readonly * @since 3.0.0 */ trimmed: { get: function () { return this.data.trim; } }, /** * The Canvas drawImage data object. * * @name Phaser.Textures.Frame#canvasData * @type {object} * @readonly * @since 3.0.0 */ canvasData: { get: function () { return this.data.drawImage; } } }); module.exports = Frame;