UNPKG

phaser

Version:

A fast, free and fun HTML5 Game Framework for Desktop and Mobile web browsers from the team at Phaser Studio Inc.

1,039 lines (882 loc) 35.4 kB
/** * @author Richard Davey <rich@phaser.io> * @author Felipe Alfonso <@bitnenfer> * @copyright 2013-2025 Phaser Studio Inc. * @license {@link https://opensource.org/licenses/MIT|MIT License} */ var Class = require('../../../utils/Class'); var Earcut = require('../../../geom/polygon/Earcut'); var GetFastValue = require('../../../utils/object/GetFastValue'); var ShaderSourceFS = require('../shaders/Multi-frag'); var ShaderSourceVS = require('../shaders/Multi-vert'); var TransformMatrix = require('../../../gameobjects/components/TransformMatrix'); var Utils = require('../Utils'); var WEBGL_CONST = require('../const'); var WebGLPipeline = require('../WebGLPipeline'); /** * @classdesc * The Multi Pipeline is the core 2D texture rendering pipeline used by Phaser in WebGL. * Virtually all Game Objects use this pipeline by default, including Sprites, Graphics * and Tilemaps. It handles the batching of quads and tris, as well as methods for * drawing and batching geometry data. * * Prior to Phaser v3.50 this pipeline was called the `TextureTintPipeline`. * * In previous versions of Phaser only one single texture unit was supported at any one time. * The Multi Pipeline is an evolution of the old Texture Tint Pipeline, updated to support * multi-textures for increased performance. * * The fragment shader it uses can be found in `shaders/src/Multi.frag`. * The vertex shader it uses can be found in `shaders/src/Multi.vert`. * * The default shader attributes for this pipeline are: * * `inPosition` (vec2, offset 0) * `inTexCoord` (vec2, offset 8) * `inTexId` (float, offset 16) * `inTintEffect` (float, offset 20) * `inTint` (vec4, offset 24, normalized) * * The default shader uniforms for this pipeline are: * * `uProjectionMatrix` (mat4) * `uResolution` (vec2) * `uMainSampler` (sampler2D, or sampler2D array) * * If you wish to create a custom pipeline extending from this one, you can use two string * declarations in your fragment shader source: `%count%` and `%forloop%`, where `count` is * used to set the number of `sampler2Ds` available, and `forloop` is a block of GLSL code * that will get the currently bound texture unit. * * This pipeline will automatically inject that code for you, should those values exist * in your shader source. If you wish to handle this yourself, you can also use the * function `Utils.parseFragmentShaderMaxTextures`. * * The following fragment shader shows how to use the two variables: * * ```glsl * #define SHADER_NAME PHASER_MULTI_FS * * #ifdef GL_FRAGMENT_PRECISION_HIGH * precision highp float; * #else * precision mediump float; * #endif * * uniform sampler2D uMainSampler[%count%]; * * varying vec2 outTexCoord; * varying float outTexId; * varying float outTintEffect; * varying vec4 outTint; * * void main () * { * vec4 texture; * * %forloop% * * vec4 texel = vec4(outTint.bgr * outTint.a, outTint.a); * * // Multiply texture tint * vec4 color = texture * texel; * * if (outTintEffect == 1.0) * { * // Solid color + texture alpha * color.rgb = mix(texture.rgb, outTint.bgr * outTint.a, texture.a); * } * else if (outTintEffect == 2.0) * { * // Solid color, no texture * color = texel; * } * * gl_FragColor = color; * } * ``` * * If you wish to create a pipeline that works from a single texture, or that doesn't have * internal texture iteration, please see the `SinglePipeline` instead. If you wish to create * a special effect, especially one that can impact the pixels around a texture (i.e. such as * a glitch effect) then you should use the PreFX and PostFX Pipelines for this task. * * @class MultiPipeline * @extends Phaser.Renderer.WebGL.WebGLPipeline * @memberof Phaser.Renderer.WebGL.Pipelines * @constructor * @since 3.50.0 * * @param {Phaser.Types.Renderer.WebGL.WebGLPipelineConfig} config - The configuration options for this pipeline. */ var MultiPipeline = new Class({ Extends: WebGLPipeline, initialize: function MultiPipeline (config) { var renderer = config.game.renderer; var fragmentShaderSource = GetFastValue(config, 'fragShader', ShaderSourceFS); config.fragShader = Utils.parseFragmentShaderMaxTextures(fragmentShaderSource, renderer.maxTextures); config.vertShader = GetFastValue(config, 'vertShader', ShaderSourceVS); config.attributes = GetFastValue(config, 'attributes', [ { name: 'inPosition', size: 2 }, { name: 'inTexCoord', size: 2 }, { name: 'inTexId' }, { name: 'inTintEffect' }, { name: 'inTint', size: 4, type: WEBGL_CONST.UNSIGNED_BYTE, normalized: true } ]); config.resizeUniform = 'uResolution'; WebGLPipeline.call(this, config); /** * A temporary Transform Matrix, re-used internally during batching. * * @name Phaser.Renderer.WebGL.Pipelines.MultiPipeline#_tempMatrix1 * @private * @type {Phaser.GameObjects.Components.TransformMatrix} * @since 3.11.0 */ this._tempMatrix1 = new TransformMatrix(); /** * A temporary Transform Matrix, re-used internally during batching. * * @name Phaser.Renderer.WebGL.Pipelines.MultiPipeline#_tempMatrix2 * @private * @type {Phaser.GameObjects.Components.TransformMatrix} * @since 3.11.0 */ this._tempMatrix2 = new TransformMatrix(); /** * A temporary Transform Matrix, re-used internally during batching. * * @name Phaser.Renderer.WebGL.Pipelines.MultiPipeline#_tempMatrix3 * @private * @type {Phaser.GameObjects.Components.TransformMatrix} * @since 3.11.0 */ this._tempMatrix3 = new TransformMatrix(); /** * A temporary Transform Matrix, re-used internally during batching by the * Shape Game Objects. * * @name Phaser.Renderer.WebGL.Pipelines.MultiPipeline#calcMatrix * @type {Phaser.GameObjects.Components.TransformMatrix} * @since 3.55.0 */ this.calcMatrix = new TransformMatrix(); /** * Used internally to draw stroked triangles. * * @name Phaser.Renderer.WebGL.Pipelines.MultiPipeline#tempTriangle * @type {array} * @private * @since 3.55.0 */ this.tempTriangle = [ { x: 0, y: 0, width: 0 }, { x: 0, y: 0, width: 0 }, { x: 0, y: 0, width: 0 }, { x: 0, y: 0, width: 0 } ]; /** * Cached stroke tint. * * @name Phaser.Renderer.WebGL.Pipelines.MultiPipeline#strokeTint * @type {object} * @private * @since 3.55.0 */ this.strokeTint = { TL: 0, TR: 0, BL: 0, BR: 0 }; /** * Cached fill tint. * * @name Phaser.Renderer.WebGL.Pipelines.MultiPipeline#fillTint * @type {object} * @private * @since 3.55.0 */ this.fillTint = { TL: 0, TR: 0, BL: 0, BR: 0 }; /** * Internal texture frame reference. * * @name Phaser.Renderer.WebGL.Pipelines.MultiPipeline#currentFrame * @type {Phaser.Textures.Frame} * @private * @since 3.55.0 */ this.currentFrame = { u0: 0, v0: 0, u1: 1, v1: 1 }; /** * Internal path quad cache. * * @name Phaser.Renderer.WebGL.Pipelines.MultiPipeline#firstQuad * @type {number[]} * @private * @since 3.55.0 */ this.firstQuad = [ 0, 0, 0, 0, 0 ]; /** * Internal path quad cache. * * @name Phaser.Renderer.WebGL.Pipelines.MultiPipeline#prevQuad * @type {number[]} * @private * @since 3.55.0 */ this.prevQuad = [ 0, 0, 0, 0, 0 ]; /** * Used internally for triangulating a polygon. * * @name Phaser.Renderer.WebGL.Pipelines.MultiPipeline#polygonCache * @type {array} * @private * @since 3.55.0 */ this.polygonCache = []; }, /** * Called every time the pipeline is bound by the renderer. * Sets the shader program, vertex buffer and other resources. * Should only be called when changing pipeline. * * @method Phaser.Renderer.WebGL.Pipelines.MultiPipeline#boot * @since 3.50.0 */ boot: function () { WebGLPipeline.prototype.boot.call(this); var renderer = this.renderer; this.set1iv('uMainSampler', renderer.textureIndexes); this.set2f('uResolution', renderer.width, renderer.height); }, /** * Takes a Sprite Game Object, or any object that extends it, and adds it to the batch. * * @method Phaser.Renderer.WebGL.Pipelines.MultiPipeline#batchSprite * @since 3.0.0 * * @param {(Phaser.GameObjects.Image|Phaser.GameObjects.Sprite)} gameObject - The texture based Game Object to add to the batch. * @param {Phaser.Cameras.Scene2D.Camera} camera - The Camera to use for the rendering transform. * @param {Phaser.GameObjects.Components.TransformMatrix} [parentTransformMatrix] - The transform matrix of the parent container, if set. */ batchSprite: function (gameObject, camera, parentTransformMatrix) { this.manager.set(this, gameObject); var camMatrix = this._tempMatrix1; var spriteMatrix = this._tempMatrix2; var calcMatrix = this._tempMatrix3; var frame = gameObject.frame; var texture = frame.glTexture; var u0 = frame.u0; var v0 = frame.v0; var u1 = frame.u1; var v1 = frame.v1; var frameX = frame.x; var frameY = frame.y; var frameWidth = frame.cutWidth; var frameHeight = frame.cutHeight; var customPivot = frame.customPivot; var displayOriginX = gameObject.displayOriginX; var displayOriginY = gameObject.displayOriginY; var x = -displayOriginX + frameX; var y = -displayOriginY + frameY; if (gameObject.isCropped) { var crop = gameObject._crop; if (crop.flipX !== gameObject.flipX || crop.flipY !== gameObject.flipY) { frame.updateCropUVs(crop, gameObject.flipX, gameObject.flipY); } u0 = crop.u0; v0 = crop.v0; u1 = crop.u1; v1 = crop.v1; frameWidth = crop.width; frameHeight = crop.height; frameX = crop.x; frameY = crop.y; x = -displayOriginX + frameX; y = -displayOriginY + frameY; } var flipX = 1; var flipY = 1; if (gameObject.flipX) { if (!customPivot) { x += (-frame.realWidth + (displayOriginX * 2)); } flipX = -1; } if (gameObject.flipY) { if (!customPivot) { y += (-frame.realHeight + (displayOriginY * 2)); } flipY = -1; } var gx = gameObject.x; var gy = gameObject.y; if (camera.roundPixels) { gx = Math.floor(gx); gy = Math.floor(gy); } spriteMatrix.applyITRS(gx, gy, gameObject.rotation, gameObject.scaleX * flipX, gameObject.scaleY * flipY); camMatrix.copyFrom(camera.matrix); if (parentTransformMatrix) { // Multiply the camera by the parent matrix camMatrix.multiplyWithOffset(parentTransformMatrix, -camera.scrollX * gameObject.scrollFactorX, -camera.scrollY * gameObject.scrollFactorY); // Undo the camera scroll spriteMatrix.e = gx; spriteMatrix.f = gy; } else { spriteMatrix.e -= camera.scrollX * gameObject.scrollFactorX; spriteMatrix.f -= camera.scrollY * gameObject.scrollFactorY; } // Multiply by the Sprite matrix, store result in calcMatrix camMatrix.multiply(spriteMatrix, calcMatrix); var quad = calcMatrix.setQuad(x, y, x + frameWidth, y + frameHeight, camera.renderRoundPixels); var getTint = Utils.getTintAppendFloatAlpha; var cameraAlpha = camera.alpha; var tintTL = getTint(gameObject.tintTopLeft, cameraAlpha * gameObject._alphaTL); var tintTR = getTint(gameObject.tintTopRight, cameraAlpha * gameObject._alphaTR); var tintBL = getTint(gameObject.tintBottomLeft, cameraAlpha * gameObject._alphaBL); var tintBR = getTint(gameObject.tintBottomRight, cameraAlpha * gameObject._alphaBR); if (this.shouldFlush(6)) { this.flush(); } var unit = this.setGameObject(gameObject, frame); this.manager.preBatch(gameObject); this.batchQuad(gameObject, quad[0], quad[1], quad[2], quad[3], quad[4], quad[5], quad[6], quad[7], u0, v0, u1, v1, tintTL, tintTR, tintBL, tintBR, gameObject.tintFill, texture, unit); this.manager.postBatch(gameObject); }, /** * Generic function for batching a textured quad using argument values instead of a Game Object. * * @method Phaser.Renderer.WebGL.Pipelines.MultiPipeline#batchTexture * @since 3.0.0 * * @param {Phaser.GameObjects.GameObject} gameObject - Source GameObject. * @param {Phaser.Renderer.WebGL.Wrappers.WebGLTextureWrapper} texture - Texture associated with the quad. * @param {number} textureWidth - Real texture width. * @param {number} textureHeight - Real texture height. * @param {number} srcX - X coordinate of the quad. * @param {number} srcY - Y coordinate of the quad. * @param {number} srcWidth - Width of the quad. * @param {number} srcHeight - Height of the quad. * @param {number} scaleX - X component of scale. * @param {number} scaleY - Y component of scale. * @param {number} rotation - Rotation of the quad. * @param {boolean} flipX - Indicates if the quad is horizontally flipped. * @param {boolean} flipY - Indicates if the quad is vertically flipped. * @param {number} scrollFactorX - By which factor is the quad affected by the camera horizontal scroll. * @param {number} scrollFactorY - By which factor is the quad effected by the camera vertical scroll. * @param {number} displayOriginX - Horizontal origin in pixels. * @param {number} displayOriginY - Vertical origin in pixels. * @param {number} frameX - X coordinate of the texture frame. * @param {number} frameY - Y coordinate of the texture frame. * @param {number} frameWidth - Width of the texture frame. * @param {number} frameHeight - Height of the texture frame. * @param {number} tintTL - Tint for top left. * @param {number} tintTR - Tint for top right. * @param {number} tintBL - Tint for bottom left. * @param {number} tintBR - Tint for bottom right. * @param {number} tintEffect - The tint effect. * @param {number} uOffset - Horizontal offset on texture coordinate. * @param {number} vOffset - Vertical offset on texture coordinate. * @param {Phaser.Cameras.Scene2D.Camera} camera - Current used camera. * @param {Phaser.GameObjects.Components.TransformMatrix} parentTransformMatrix - Parent container. * @param {boolean} [skipFlip=false] - Skip the renderTexture check. * @param {number} [textureUnit] - The texture unit to set (defaults to currently bound if undefined or null) * @param {boolean} [skipPrePost=false] - Skip the pre and post manager calls? */ batchTexture: function ( gameObject, texture, textureWidth, textureHeight, srcX, srcY, srcWidth, srcHeight, scaleX, scaleY, rotation, flipX, flipY, scrollFactorX, scrollFactorY, displayOriginX, displayOriginY, frameX, frameY, frameWidth, frameHeight, tintTL, tintTR, tintBL, tintBR, tintEffect, uOffset, vOffset, camera, parentTransformMatrix, skipFlip, textureUnit, skipPrePost) { if (skipPrePost === undefined) { skipPrePost = false; } this.manager.set(this, gameObject); var camMatrix = this._tempMatrix1; var spriteMatrix = this._tempMatrix2; var calcMatrix = this._tempMatrix3; var u0 = (frameX / textureWidth) + uOffset; var v0 = (frameY / textureHeight) + vOffset; var u1 = (frameX + frameWidth) / textureWidth + uOffset; var v1 = (frameY + frameHeight) / textureHeight + vOffset; var width = srcWidth; var height = srcHeight; var x = -displayOriginX; var y = -displayOriginY; if (gameObject.isCropped) { var crop = gameObject._crop; var cropWidth = crop.width; var cropHeight = crop.height; width = cropWidth; height = cropHeight; srcWidth = cropWidth; srcHeight = cropHeight; frameX = crop.x; frameY = crop.y; var ox = frameX; var oy = frameY; if (flipX) { ox = (frameWidth - crop.x - cropWidth); } if (flipY) { oy = (frameHeight - crop.y - cropHeight); } u0 = (ox / textureWidth) + uOffset; v0 = (oy / textureHeight) + vOffset; u1 = (ox + cropWidth) / textureWidth + uOffset; v1 = (oy + cropHeight) / textureHeight + vOffset; x = -displayOriginX + frameX; y = -displayOriginY + frameY; } // Invert the flipY if this is a RenderTexture flipY = flipY ^ (!skipFlip && texture.isRenderTexture ? 1 : 0); if (flipX) { width *= -1; x += srcWidth; } if (flipY) { height *= -1; y += srcHeight; } if (camera.roundPixels) { srcX = Math.floor(srcX); srcY = Math.floor(srcY); } spriteMatrix.applyITRS(srcX, srcY, rotation, scaleX, scaleY); camMatrix.copyFrom(camera.matrix); if (parentTransformMatrix) { // Multiply the camera by the parent matrix camMatrix.multiplyWithOffset(parentTransformMatrix, -camera.scrollX * scrollFactorX, -camera.scrollY * scrollFactorY); // Undo the camera scroll spriteMatrix.e = srcX; spriteMatrix.f = srcY; } else { spriteMatrix.e -= camera.scrollX * scrollFactorX; spriteMatrix.f -= camera.scrollY * scrollFactorY; } // Multiply by the Sprite matrix, store result in calcMatrix camMatrix.multiply(spriteMatrix, calcMatrix); var quad = calcMatrix.setQuad(x, y, x + width, y + height, camera.renderRoundPixels); if (textureUnit === undefined || textureUnit === null) { textureUnit = this.setTexture2D(texture); } if (gameObject && !skipPrePost) { this.manager.preBatch(gameObject); } this.batchQuad(gameObject, quad[0], quad[1], quad[2], quad[3], quad[4], quad[5], quad[6], quad[7], u0, v0, u1, v1, tintTL, tintTR, tintBL, tintBR, tintEffect, texture, textureUnit); if (gameObject && !skipPrePost) { this.manager.postBatch(gameObject); } }, /** * Adds a Texture Frame into the batch for rendering. * * @method Phaser.Renderer.WebGL.Pipelines.MultiPipeline#batchTextureFrame * @since 3.12.0 * * @param {Phaser.Textures.Frame} frame - The Texture Frame to be rendered. * @param {number} x - The horizontal position to render the texture at. * @param {number} y - The vertical position to render the texture at. * @param {number} tint - The tint color. * @param {number} alpha - The alpha value. * @param {Phaser.GameObjects.Components.TransformMatrix} transformMatrix - The Transform Matrix to use for the texture. * @param {Phaser.GameObjects.Components.TransformMatrix} [parentTransformMatrix] - A parent Transform Matrix. */ batchTextureFrame: function ( frame, x, y, tint, alpha, transformMatrix, parentTransformMatrix ) { this.manager.set(this); var spriteMatrix = this._tempMatrix1.copyFrom(transformMatrix); var calcMatrix = this._tempMatrix2; if (parentTransformMatrix) { spriteMatrix.multiply(parentTransformMatrix, calcMatrix); } else { calcMatrix = spriteMatrix; } var quad = calcMatrix.setQuad(x, y, x + frame.width, y + frame.height); var unit = this.setTexture2D(frame.source.glTexture); tint = Utils.getTintAppendFloatAlpha(tint, alpha); this.batchQuad(null, quad[0], quad[1], quad[2], quad[3], quad[4], quad[5], quad[6], quad[7], frame.u0, frame.v0, frame.u1, frame.v1, tint, tint, tint, tint, 0, frame.glTexture, unit); }, /** * Pushes a filled rectangle into the vertex batch. * * Rectangle factors in the given transform matrices before adding to the batch. * * @method Phaser.Renderer.WebGL.Pipelines.MultiPipeline#batchFillRect * @since 3.55.0 * * @param {number} x - Horizontal top left coordinate of the rectangle. * @param {number} y - Vertical top left coordinate of the rectangle. * @param {number} width - Width of the rectangle. * @param {number} height - Height of the rectangle. * @param {Phaser.GameObjects.Components.TransformMatrix} currentMatrix - The current transform. * @param {Phaser.GameObjects.Components.TransformMatrix} parentMatrix - The parent transform. */ batchFillRect: function (x, y, width, height, currentMatrix, parentMatrix) { this.renderer.pipelines.set(this); var calcMatrix = this.calcMatrix; // Multiply and store result in calcMatrix, only if the parentMatrix is set, otherwise we'll use whatever values are already in the calcMatrix if (parentMatrix) { parentMatrix.multiply(currentMatrix, calcMatrix); } var quad = calcMatrix.setQuad(x, y, x + width, y + height); var tint = this.fillTint; this.batchQuad(null, quad[0], quad[1], quad[2], quad[3], quad[4], quad[5], quad[6], quad[7], 0, 0, 1, 1, tint.TL, tint.TR, tint.BL, tint.BR, 2); }, /** * Pushes a filled triangle into the vertex batch. * * Triangle factors in the given transform matrices before adding to the batch. * * @method Phaser.Renderer.WebGL.Pipelines.MultiPipeline#batchFillTriangle * @since 3.55.0 * * @param {number} x0 - Point 0 x coordinate. * @param {number} y0 - Point 0 y coordinate. * @param {number} x1 - Point 1 x coordinate. * @param {number} y1 - Point 1 y coordinate. * @param {number} x2 - Point 2 x coordinate. * @param {number} y2 - Point 2 y coordinate. * @param {Phaser.GameObjects.Components.TransformMatrix} currentMatrix - The current transform. * @param {Phaser.GameObjects.Components.TransformMatrix} parentMatrix - The parent transform. */ batchFillTriangle: function (x0, y0, x1, y1, x2, y2, currentMatrix, parentMatrix) { this.renderer.pipelines.set(this); var calcMatrix = this.calcMatrix; // Multiply and store result in calcMatrix, only if the parentMatrix is set, otherwise we'll use whatever values are already in the calcMatrix if (parentMatrix) { parentMatrix.multiply(currentMatrix, calcMatrix); } var tx0 = calcMatrix.getX(x0, y0); var ty0 = calcMatrix.getY(x0, y0); var tx1 = calcMatrix.getX(x1, y1); var ty1 = calcMatrix.getY(x1, y1); var tx2 = calcMatrix.getX(x2, y2); var ty2 = calcMatrix.getY(x2, y2); var tint = this.fillTint; this.batchTri(null, tx0, ty0, tx1, ty1, tx2, ty2, 0, 0, 1, 1, tint.TL, tint.TR, tint.BL, 2); }, /** * Pushes a stroked triangle into the vertex batch. * * Triangle factors in the given transform matrices before adding to the batch. * * The triangle is created from 3 lines and drawn using the `batchStrokePath` method. * * @method Phaser.Renderer.WebGL.Pipelines.MultiPipeline#batchStrokeTriangle * @since 3.55.0 * * @param {number} x0 - Point 0 x coordinate. * @param {number} y0 - Point 0 y coordinate. * @param {number} x1 - Point 1 x coordinate. * @param {number} y1 - Point 1 y coordinate. * @param {number} x2 - Point 2 x coordinate. * @param {number} y2 - Point 2 y coordinate. * @param {number} lineWidth - The width of the line in pixels. * @param {Phaser.GameObjects.Components.TransformMatrix} currentMatrix - The current transform. * @param {Phaser.GameObjects.Components.TransformMatrix} parentMatrix - The parent transform. */ batchStrokeTriangle: function (x0, y0, x1, y1, x2, y2, lineWidth, currentMatrix, parentMatrix) { var tempTriangle = this.tempTriangle; tempTriangle[0].x = x0; tempTriangle[0].y = y0; tempTriangle[0].width = lineWidth; tempTriangle[1].x = x1; tempTriangle[1].y = y1; tempTriangle[1].width = lineWidth; tempTriangle[2].x = x2; tempTriangle[2].y = y2; tempTriangle[2].width = lineWidth; tempTriangle[3].x = x0; tempTriangle[3].y = y0; tempTriangle[3].width = lineWidth; this.batchStrokePath(tempTriangle, lineWidth, false, currentMatrix, parentMatrix); }, /** * Adds the given path to the vertex batch for rendering. * * It works by taking the array of path data and then passing it through Earcut, which * creates a list of polygons. Each polygon is then added to the batch. * * The path is always automatically closed because it's filled. * * @method Phaser.Renderer.WebGL.Pipelines.MultiPipeline#batchFillPath * @since 3.55.0 * * @param {Phaser.Types.Math.Vector2Like[]} path - Collection of points that represent the path. * @param {Phaser.GameObjects.Components.TransformMatrix} currentMatrix - The current transform. * @param {Phaser.GameObjects.Components.TransformMatrix} parentMatrix - The parent transform. */ batchFillPath: function (path, currentMatrix, parentMatrix) { this.renderer.pipelines.set(this); var calcMatrix = this.calcMatrix; // Multiply and store result in calcMatrix, only if the parentMatrix is set, otherwise we'll use whatever values are already in the calcMatrix if (parentMatrix) { parentMatrix.multiply(currentMatrix, calcMatrix); } var length = path.length; var polygonCache = this.polygonCache; var polygonIndexArray; var point; var tintTL = this.fillTint.TL; var tintTR = this.fillTint.TR; var tintBL = this.fillTint.BL; for (var pathIndex = 0; pathIndex < length; ++pathIndex) { point = path[pathIndex]; polygonCache.push(point.x, point.y); } polygonIndexArray = Earcut(polygonCache); length = polygonIndexArray.length; for (var index = 0; index < length; index += 3) { var p0 = polygonIndexArray[index + 0] * 2; var p1 = polygonIndexArray[index + 1] * 2; var p2 = polygonIndexArray[index + 2] * 2; var x0 = polygonCache[p0 + 0]; var y0 = polygonCache[p0 + 1]; var x1 = polygonCache[p1 + 0]; var y1 = polygonCache[p1 + 1]; var x2 = polygonCache[p2 + 0]; var y2 = polygonCache[p2 + 1]; var tx0 = calcMatrix.getX(x0, y0); var ty0 = calcMatrix.getY(x0, y0); var tx1 = calcMatrix.getX(x1, y1); var ty1 = calcMatrix.getY(x1, y1); var tx2 = calcMatrix.getX(x2, y2); var ty2 = calcMatrix.getY(x2, y2); this.batchTri(null, tx0, ty0, tx1, ty1, tx2, ty2, 0, 0, 1, 1, tintTL, tintTR, tintBL, 2); } polygonCache.length = 0; }, /** * Adds the given path to the vertex batch for rendering. * * It works by taking the array of path data and calling `batchLine` for each section * of the path. * * The path is optionally closed at the end. * * @method Phaser.Renderer.WebGL.Pipelines.MultiPipeline#batchStrokePath * @since 3.55.0 * * @param {Phaser.Types.Math.Vector2Like[]} path - Collection of points that represent the path. * @param {number} lineWidth - The width of the line segments in pixels. * @param {boolean} pathOpen - Indicates if the path should be closed or left open. * @param {Phaser.GameObjects.Components.TransformMatrix} currentMatrix - The current transform. * @param {Phaser.GameObjects.Components.TransformMatrix} parentMatrix - The parent transform. */ batchStrokePath: function (path, lineWidth, pathOpen, currentMatrix, parentMatrix) { this.renderer.pipelines.set(this); // Reset the closePath booleans this.prevQuad[4] = 0; this.firstQuad[4] = 0; var pathLength = path.length - 1; for (var pathIndex = 0; pathIndex < pathLength; pathIndex++) { var point0 = path[pathIndex]; var point1 = path[pathIndex + 1]; this.batchLine( point0.x, point0.y, point1.x, point1.y, point0.width / 2, point1.width / 2, lineWidth, pathIndex, !pathOpen && (pathIndex === pathLength - 1), currentMatrix, parentMatrix ); } }, /** * Creates a line out of 4 quads and adds it to the vertex batch based on the given line values. * * @method Phaser.Renderer.WebGL.Pipelines.MultiPipeline#batchLine * @since 3.55.0 * * @param {number} ax - x coordinate of the start of the line. * @param {number} ay - y coordinate of the start of the line. * @param {number} bx - x coordinate of the end of the line. * @param {number} by - y coordinate of the end of the line. * @param {number} aLineWidth - Width of the start of the line. * @param {number} bLineWidth - Width of the end of the line. * @param {number} index - If this line is part of a multi-line draw, the index of the line in the draw. * @param {boolean} closePath - Does this line close a multi-line path? * @param {Phaser.GameObjects.Components.TransformMatrix} currentMatrix - The current transform. * @param {Phaser.GameObjects.Components.TransformMatrix} parentMatrix - The parent transform. */ batchLine: function (ax, ay, bx, by, aLineWidth, bLineWidth, lineWidth, index, closePath, currentMatrix, parentMatrix) { this.renderer.pipelines.set(this); var calcMatrix = this.calcMatrix; // Multiply and store result in calcMatrix, only if the parentMatrix is set, otherwise we'll use whatever values are already in the calcMatrix if (parentMatrix) { parentMatrix.multiply(currentMatrix, calcMatrix); } var dx = bx - ax; var dy = by - ay; var len = Math.sqrt(dx * dx + dy * dy); if (len === 0) { // Because we cannot (and should not) divide by zero! return; } var al0 = aLineWidth * (by - ay) / len; var al1 = aLineWidth * (ax - bx) / len; var bl0 = bLineWidth * (by - ay) / len; var bl1 = bLineWidth * (ax - bx) / len; var lx0 = bx - bl0; var ly0 = by - bl1; var lx1 = ax - al0; var ly1 = ay - al1; var lx2 = bx + bl0; var ly2 = by + bl1; var lx3 = ax + al0; var ly3 = ay + al1; // tx0 = bottom right var brX = calcMatrix.getX(lx0, ly0); var brY = calcMatrix.getY(lx0, ly0); // tx1 = bottom left var blX = calcMatrix.getX(lx1, ly1); var blY = calcMatrix.getY(lx1, ly1); // tx2 = top right var trX = calcMatrix.getX(lx2, ly2); var trY = calcMatrix.getY(lx2, ly2); // tx3 = top left var tlX = calcMatrix.getX(lx3, ly3); var tlY = calcMatrix.getY(lx3, ly3); var tint = this.strokeTint; var tintTL = tint.TL; var tintTR = tint.TR; var tintBL = tint.BL; var tintBR = tint.BR; // TL, BL, BR, TR this.batchQuad(null, tlX, tlY, blX, blY, brX, brY, trX, trY, 0, 0, 1, 1, tintTL, tintTR, tintBL, tintBR, 2); if (lineWidth <= 2) { // No point doing a linejoin if the line isn't thick enough return; } var prev = this.prevQuad; var first = this.firstQuad; if (index > 0 && prev[4]) { this.batchQuad(null, tlX, tlY, blX, blY, prev[0], prev[1], prev[2], prev[3], 0, 0, 1, 1, tintTL, tintTR, tintBL, tintBR, 2); } else { first[0] = tlX; first[1] = tlY; first[2] = blX; first[3] = blY; first[4] = 1; } if (closePath && first[4]) { // Add a join for the final path segment this.batchQuad(null, brX, brY, trX, trY, first[0], first[1], first[2], first[3], 0, 0, 1, 1, tintTL, tintTR, tintBL, tintBR, 2); } else { // Store it prev[0] = brX; prev[1] = brY; prev[2] = trX; prev[3] = trY; prev[4] = 1; } }, /** * Destroys all shader instances, removes all object references and nulls all external references. * * @method Phaser.Renderer.WebGL.Pipelines.MultiPipeline#destroy * @fires Phaser.Renderer.WebGL.Pipelines.Events#DESTROY * @since 3.60.0 * * @return {this} This WebGLPipeline instance. */ destroy: function () { this._tempMatrix1.destroy(); this._tempMatrix2.destroy(); this._tempMatrix3.destroy(); this._tempMatrix1 = null; this._tempMatrix1 = null; this._tempMatrix1 = null; WebGLPipeline.prototype.destroy.call(this); return this; } }); module.exports = MultiPipeline;