phaser-multires
Version:
A fast, free and fun HTML5 Game Framework for Desktop and Mobile web browsers.
1,788 lines (1,468 loc) • 101 kB
JavaScript
/**
* @author Mat Groves http://matgroves.com/ @Doormat23
*/
/**
* The Graphics class contains methods used to draw primitive shapes such as lines, circles and rectangles to the display, and color and fill them.
*
* @class Graphics
* @extends DisplayObjectContainer
* @constructor
*/
PIXI.Graphics = function()
{
PIXI.DisplayObjectContainer.call(this);
this.renderable = true;
/**
* The alpha value used when filling the Graphics object.
*
* @property fillAlpha
* @type Number
*/
this.fillAlpha = 1;
/**
* The width (thickness) of any lines drawn.
*
* @property lineWidth
* @type Number
*/
this.lineWidth = 0;
/**
* The color of any lines drawn.
*
* @property lineColor
* @type String
* @default 0
*/
this.lineColor = 0;
/**
* Graphics data
*
* @property graphicsData
* @type Array
* @private
*/
this.graphicsData = [];
/**
* The tint applied to the graphic shape. This is a hex value. Apply a value of 0xFFFFFF to reset the tint.
*
* @property tint
* @type Number
* @default 0xFFFFFF
*/
this.tint = 0xFFFFFF;
/**
* The blend mode to be applied to the graphic shape. Apply a value of PIXI.blendModes.NORMAL to reset the blend mode.
*
* @property blendMode
* @type Number
* @default PIXI.blendModes.NORMAL;
*/
this.blendMode = PIXI.blendModes.NORMAL;
/**
* Current path
*
* @property currentPath
* @type Object
* @private
*/
this.currentPath = null;
/**
* Array containing some WebGL-related properties used by the WebGL renderer.
*
* @property _webGL
* @type Array
* @private
*/
this._webGL = [];
/**
* Whether this shape is being used as a mask.
*
* @property isMask
* @type Boolean
*/
this.isMask = false;
/**
* The bounds' padding used for bounds calculation.
*
* @property boundsPadding
* @type Number
*/
this.boundsPadding = 0;
this._localBounds = new PIXI.Rectangle(0,0,1,1);
/**
* Used to detect if the graphics object has changed. If this is set to true then the graphics object will be recalculated.
*
* @property dirty
* @type Boolean
* @private
*/
this.dirty = true;
/**
* Used to detect if the webgl graphics object has changed. If this is set to true then the graphics object will be recalculated.
*
* @property webGLDirty
* @type Boolean
* @private
*/
this.webGLDirty = false;
/**
* Used to detect if the cached sprite object needs to be updated.
*
* @property cachedSpriteDirty
* @type Boolean
* @private
*/
this.cachedSpriteDirty = false;
};
// constructor
PIXI.Graphics.prototype = Object.create( PIXI.DisplayObjectContainer.prototype );
PIXI.Graphics.prototype.constructor = PIXI.Graphics;
/**
* Specifies the line style used for subsequent calls to Graphics methods such as the lineTo() method or the drawCircle() method.
*
* @method lineStyle
* @param lineWidth {Number} width of the line to draw, will update the objects stored style
* @param color {Number} color of the line to draw, will update the objects stored style
* @param alpha {Number} alpha of the line to draw, will update the objects stored style
* @return {Graphics}
*/
PIXI.Graphics.prototype.lineStyle = function(lineWidth, color, alpha)
{
this.lineWidth = lineWidth || 0;
this.lineColor = color || 0;
this.lineAlpha = (alpha === undefined) ? 1 : alpha;
if (this.currentPath)
{
if (this.currentPath.shape.points.length)
{
// halfway through a line? start a new one!
this.drawShape(new PIXI.Polygon(this.currentPath.shape.points.slice(-2)));
}
else
{
// otherwise its empty so lets just set the line properties
this.currentPath.lineWidth = this.lineWidth;
this.currentPath.lineColor = this.lineColor;
this.currentPath.lineAlpha = this.lineAlpha;
}
}
return this;
};
/**
* Moves the current drawing position to x, y.
*
* @method moveTo
* @param x {Number} the X coordinate to move to
* @param y {Number} the Y coordinate to move to
* @return {Graphics}
*/
PIXI.Graphics.prototype.moveTo = function(x, y)
{
this.drawShape(new PIXI.Polygon([x, y]));
return this;
};
/**
* Draws a line using the current line style from the current drawing position to (x, y);
* The current drawing position is then set to (x, y).
*
* @method lineTo
* @param x {Number} the X coordinate to draw to
* @param y {Number} the Y coordinate to draw to
* @return {Graphics}
*/
PIXI.Graphics.prototype.lineTo = function(x, y)
{
if (!this.currentPath)
{
this.moveTo(0, 0);
}
this.currentPath.shape.points.push(x, y);
this.dirty = true;
return this;
};
/**
* Calculate the points for a quadratic bezier curve and then draws it.
* Based on: https://stackoverflow.com/questions/785097/how-do-i-implement-a-bezier-curve-in-c
*
* @method quadraticCurveTo
* @param cpX {Number} Control point x
* @param cpY {Number} Control point y
* @param toX {Number} Destination point x
* @param toY {Number} Destination point y
* @return {Graphics}
*/
PIXI.Graphics.prototype.quadraticCurveTo = function(cpX, cpY, toX, toY)
{
if (this.currentPath)
{
if (this.currentPath.shape.points.length === 0)
{
this.currentPath.shape.points = [0, 0];
}
}
else
{
this.moveTo(0,0);
}
var xa,
ya,
n = 20,
points = this.currentPath.shape.points;
if (points.length === 0)
{
this.moveTo(0, 0);
}
var fromX = points[points.length - 2];
var fromY = points[points.length - 1];
var j = 0;
for (var i = 1; i <= n; ++i)
{
j = i / n;
xa = fromX + ( (cpX - fromX) * j );
ya = fromY + ( (cpY - fromY) * j );
points.push( xa + ( ((cpX + ( (toX - cpX) * j )) - xa) * j ),
ya + ( ((cpY + ( (toY - cpY) * j )) - ya) * j ) );
}
this.dirty = true;
return this;
};
/**
* Calculate the points for a bezier curve and then draws it.
*
* @method bezierCurveTo
* @param cpX {Number} Control point x
* @param cpY {Number} Control point y
* @param cpX2 {Number} Second Control point x
* @param cpY2 {Number} Second Control point y
* @param toX {Number} Destination point x
* @param toY {Number} Destination point y
* @return {Graphics}
*/
PIXI.Graphics.prototype.bezierCurveTo = function(cpX, cpY, cpX2, cpY2, toX, toY)
{
if (this.currentPath)
{
if (this.currentPath.shape.points.length === 0)
{
this.currentPath.shape.points = [0, 0];
}
}
else
{
this.moveTo(0,0);
}
var n = 20,
dt,
dt2,
dt3,
t2,
t3,
points = this.currentPath.shape.points;
var fromX = points[points.length-2];
var fromY = points[points.length-1];
var j = 0;
for (var i = 1; i <= n; ++i)
{
j = i / n;
dt = (1 - j);
dt2 = dt * dt;
dt3 = dt2 * dt;
t2 = j * j;
t3 = t2 * j;
points.push( dt3 * fromX + 3 * dt2 * j * cpX + 3 * dt * t2 * cpX2 + t3 * toX,
dt3 * fromY + 3 * dt2 * j * cpY + 3 * dt * t2 * cpY2 + t3 * toY);
}
this.dirty = true;
return this;
};
/*
* The arcTo() method creates an arc/curve between two tangents on the canvas.
*
* "borrowed" from https://code.google.com/p/fxcanvas/ - thanks google!
*
* @method arcTo
* @param x1 {Number} The x-coordinate of the beginning of the arc
* @param y1 {Number} The y-coordinate of the beginning of the arc
* @param x2 {Number} The x-coordinate of the end of the arc
* @param y2 {Number} The y-coordinate of the end of the arc
* @param radius {Number} The radius of the arc
* @return {Graphics}
*/
PIXI.Graphics.prototype.arcTo = function(x1, y1, x2, y2, radius)
{
if (this.currentPath)
{
if (this.currentPath.shape.points.length === 0)
{
this.currentPath.shape.points.push(x1, y1);
}
}
else
{
this.moveTo(x1, y1);
}
var points = this.currentPath.shape.points,
fromX = points[points.length-2],
fromY = points[points.length-1],
a1 = fromY - y1,
b1 = fromX - x1,
a2 = y2 - y1,
b2 = x2 - x1,
mm = Math.abs(a1 * b2 - b1 * a2);
if (mm < 1.0e-8 || radius === 0)
{
if (points[points.length-2] !== x1 || points[points.length-1] !== y1)
{
points.push(x1, y1);
}
}
else
{
var dd = a1 * a1 + b1 * b1,
cc = a2 * a2 + b2 * b2,
tt = a1 * a2 + b1 * b2,
k1 = radius * Math.sqrt(dd) / mm,
k2 = radius * Math.sqrt(cc) / mm,
j1 = k1 * tt / dd,
j2 = k2 * tt / cc,
cx = k1 * b2 + k2 * b1,
cy = k1 * a2 + k2 * a1,
px = b1 * (k2 + j1),
py = a1 * (k2 + j1),
qx = b2 * (k1 + j2),
qy = a2 * (k1 + j2),
startAngle = Math.atan2(py - cy, px - cx),
endAngle = Math.atan2(qy - cy, qx - cx);
this.arc(cx + x1, cy + y1, radius, startAngle, endAngle, b1 * a2 > b2 * a1);
}
this.dirty = true;
return this;
};
/**
* The arc method creates an arc/curve (used to create circles, or parts of circles).
*
* @method arc
* @param cx {Number} The x-coordinate of the center of the circle
* @param cy {Number} The y-coordinate of the center of the circle
* @param radius {Number} The radius of the circle
* @param startAngle {Number} The starting angle, in radians (0 is at the 3 o'clock position of the arc's circle)
* @param endAngle {Number} The ending angle, in radians
* @param anticlockwise {Boolean} Optional. Specifies whether the drawing should be counterclockwise or clockwise. False is default, and indicates clockwise, while true indicates counter-clockwise.
* @param segments {Number} Optional. The number of segments to use when calculating the arc. The default is 40. If you need more fidelity use a higher number.
* @return {Graphics}
*/
PIXI.Graphics.prototype.arc = function(cx, cy, radius, startAngle, endAngle, anticlockwise, segments)
{
// If we do this we can never draw a full circle
if (startAngle === endAngle)
{
return this;
}
if (anticlockwise === undefined) { anticlockwise = false; }
if (segments === undefined) { segments = 40; }
if (!anticlockwise && endAngle <= startAngle)
{
endAngle += Math.PI * 2;
}
else if (anticlockwise && startAngle <= endAngle)
{
startAngle += Math.PI * 2;
}
var sweep = anticlockwise ? (startAngle - endAngle) * -1 : (endAngle - startAngle);
var segs = Math.ceil(Math.abs(sweep) / (Math.PI * 2)) * segments;
// Sweep check - moved here because we don't want to do the moveTo below if the arc fails
if (sweep === 0)
{
return this;
}
var startX = cx + Math.cos(startAngle) * radius;
var startY = cy + Math.sin(startAngle) * radius;
if (anticlockwise && this.filling)
{
this.moveTo(cx, cy);
}
else
{
this.moveTo(startX, startY);
}
// currentPath will always exist after calling a moveTo
var points = this.currentPath.shape.points;
var theta = sweep / (segs * 2);
var theta2 = theta * 2;
var cTheta = Math.cos(theta);
var sTheta = Math.sin(theta);
var segMinus = segs - 1;
var remainder = (segMinus % 1) / segMinus;
for (var i = 0; i <= segMinus; i++)
{
var real = i + remainder * i;
var angle = ((theta) + startAngle + (theta2 * real));
var c = Math.cos(angle);
var s = -Math.sin(angle);
points.push(( (cTheta * c) + (sTheta * s) ) * radius + cx,
( (cTheta * -s) + (sTheta * c) ) * radius + cy);
}
this.dirty = true;
return this;
};
/**
* Specifies a simple one-color fill that subsequent calls to other Graphics methods
* (such as lineTo() or drawCircle()) use when drawing.
*
* @method beginFill
* @param color {Number} the color of the fill
* @param alpha {Number} the alpha of the fill
* @return {Graphics}
*/
PIXI.Graphics.prototype.beginFill = function(color, alpha)
{
this.filling = true;
this.fillColor = color || 0;
this.fillAlpha = (alpha === undefined) ? 1 : alpha;
if (this.currentPath)
{
if (this.currentPath.shape.points.length <= 2)
{
this.currentPath.fill = this.filling;
this.currentPath.fillColor = this.fillColor;
this.currentPath.fillAlpha = this.fillAlpha;
}
}
return this;
};
/**
* Applies a fill to the lines and shapes that were added since the last call to the beginFill() method.
*
* @method endFill
* @return {Graphics}
*/
PIXI.Graphics.prototype.endFill = function()
{
this.filling = false;
this.fillColor = null;
this.fillAlpha = 1;
return this;
};
/**
* @method drawRect
*
* @param x {Number} The X coord of the top-left of the rectangle
* @param y {Number} The Y coord of the top-left of the rectangle
* @param width {Number} The width of the rectangle
* @param height {Number} The height of the rectangle
* @return {Graphics}
*/
PIXI.Graphics.prototype.drawRect = function(x, y, width, height)
{
this.drawShape(new PIXI.Rectangle(x, y, width, height));
return this;
};
/**
* @method drawRoundedRect
* @param x {Number} The X coord of the top-left of the rectangle
* @param y {Number} The Y coord of the top-left of the rectangle
* @param width {Number} The width of the rectangle
* @param height {Number} The height of the rectangle
* @param radius {Number} Radius of the rectangle corners. In WebGL this must be a value between 0 and 9.
*/
PIXI.Graphics.prototype.drawRoundedRect = function(x, y, width, height, radius)
{
this.drawShape(new PIXI.RoundedRectangle(x, y, width, height, radius));
return this;
};
/**
* Draws a circle.
*
* @method drawCircle
* @param x {Number} The X coordinate of the center of the circle
* @param y {Number} The Y coordinate of the center of the circle
* @param diameter {Number} The diameter of the circle
* @return {Graphics}
*/
PIXI.Graphics.prototype.drawCircle = function(x, y, diameter)
{
this.drawShape(new PIXI.Circle(x, y, diameter));
return this;
};
/**
* Draws an ellipse.
*
* @method drawEllipse
* @param x {Number} The X coordinate of the center of the ellipse
* @param y {Number} The Y coordinate of the center of the ellipse
* @param width {Number} The half width of the ellipse
* @param height {Number} The half height of the ellipse
* @return {Graphics}
*/
PIXI.Graphics.prototype.drawEllipse = function(x, y, width, height)
{
this.drawShape(new PIXI.Ellipse(x, y, width, height));
return this;
};
/**
* Draws a polygon using the given path.
*
* @method drawPolygon
* @param path {Array|Phaser.Polygon} The path data used to construct the polygon. Can either be an array of points or a Phaser.Polygon object.
* @return {Graphics}
*/
PIXI.Graphics.prototype.drawPolygon = function(path)
{
if (path instanceof Phaser.Polygon || path instanceof PIXI.Polygon)
{
path = path.points;
}
// prevents an argument assignment deopt
// see section 3.1: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments
var points = path;
if (!Array.isArray(points))
{
// prevents an argument leak deopt
// see section 3.2: https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#3-managing-arguments
points = new Array(arguments.length);
for (var i = 0; i < points.length; ++i)
{
points[i] = arguments[i];
}
}
this.drawShape(new Phaser.Polygon(points));
return this;
};
/**
* Clears the graphics that were drawn to this Graphics object, and resets fill and line style settings.
*
* @method clear
* @return {Graphics}
*/
PIXI.Graphics.prototype.clear = function()
{
this.lineWidth = 0;
this.filling = false;
this.dirty = true;
this.clearDirty = true;
this.graphicsData = [];
return this;
};
/**
* Useful function that returns a texture of the graphics object that can then be used to create sprites
* This can be quite useful if your geometry is complicated and needs to be reused multiple times.
*
* @method generateTexture
* @param [resolution=1] {Number} The resolution of the texture being generated
* @param [scaleMode=0] {Number} Should be one of the PIXI.scaleMode consts
* @param [padding=0] {Number} Add optional extra padding to the generated texture (default 0)
* @return {Texture} a texture of the graphics object
*/
PIXI.Graphics.prototype.generateTexture = function(resolution, scaleMode, padding)
{
if (resolution === undefined) { resolution = 1; }
if (scaleMode === undefined) { scaleMode = PIXI.scaleModes.DEFAULT; }
if (padding === undefined) { padding = 0; }
var bounds = this.getBounds();
bounds.width += padding;
bounds.height += padding;
var canvasBuffer = new PIXI.CanvasBuffer(bounds.width * resolution, bounds.height * resolution);
var texture = PIXI.Texture.fromCanvas(canvasBuffer.canvas, scaleMode);
texture.baseTexture.resolution = resolution;
canvasBuffer.context.scale(resolution, resolution);
canvasBuffer.context.translate(-bounds.x, -bounds.y);
// Call here
PIXI.CanvasGraphics.renderGraphics(this, canvasBuffer.context);
return texture;
};
/**
* Renders the object using the WebGL renderer
*
* @method _renderWebGL
* @param renderSession {RenderSession}
* @private
*/
PIXI.Graphics.prototype._renderWebGL = function(renderSession)
{
// if the sprite is not visible or the alpha is 0 then no need to render this element
if (this.visible === false || this.alpha === 0 || this.isMask === true) return;
if (this._cacheAsBitmap)
{
if (this.dirty || this.cachedSpriteDirty)
{
this._generateCachedSprite();
// we will also need to update the texture on the gpu too!
this.updateCachedSpriteTexture();
this.cachedSpriteDirty = false;
this.dirty = false;
}
this._cachedSprite.worldAlpha = this.worldAlpha;
PIXI.Sprite.prototype._renderWebGL.call(this._cachedSprite, renderSession);
return;
}
else
{
renderSession.spriteBatch.stop();
renderSession.blendModeManager.setBlendMode(this.blendMode);
if (this._mask) renderSession.maskManager.pushMask(this._mask, renderSession);
if (this._filters) renderSession.filterManager.pushFilter(this._filterBlock);
// check blend mode
if (this.blendMode !== renderSession.spriteBatch.currentBlendMode)
{
renderSession.spriteBatch.currentBlendMode = this.blendMode;
var blendModeWebGL = PIXI.blendModesWebGL[renderSession.spriteBatch.currentBlendMode];
renderSession.spriteBatch.gl.blendFunc(blendModeWebGL[0], blendModeWebGL[1]);
}
// check if the webgl graphic needs to be updated
if (this.webGLDirty)
{
this.dirty = true;
this.webGLDirty = false;
}
PIXI.WebGLGraphics.renderGraphics(this, renderSession);
// only render if it has children!
if (this.children.length)
{
renderSession.spriteBatch.start();
// simple render children!
for (var i = 0; i < this.children.length; i++)
{
this.children[i]._renderWebGL(renderSession);
}
renderSession.spriteBatch.stop();
}
if (this._filters) renderSession.filterManager.popFilter();
if (this._mask) renderSession.maskManager.popMask(this.mask, renderSession);
renderSession.drawCount++;
renderSession.spriteBatch.start();
}
};
/**
* Renders the object using the Canvas renderer
*
* @method _renderCanvas
* @param renderSession {RenderSession}
* @private
*/
PIXI.Graphics.prototype._renderCanvas = function(renderSession)
{
// if the sprite is not visible or the alpha is 0 then no need to render this element
if (this.visible === false || this.alpha === 0 || this.isMask === true) return;
// if the tint has changed, set the graphics object to dirty.
if (this._prevTint !== this.tint) {
this.dirty = true;
this._prevTint = this.tint;
}
if (this._cacheAsBitmap)
{
if (this.dirty || this.cachedSpriteDirty)
{
this._generateCachedSprite();
// we will also need to update the texture
this.updateCachedSpriteTexture();
this.cachedSpriteDirty = false;
this.dirty = false;
}
this._cachedSprite.alpha = this.alpha;
PIXI.Sprite.prototype._renderCanvas.call(this._cachedSprite, renderSession);
return;
}
else
{
var context = renderSession.context;
var transform = this.worldTransform;
if (this.blendMode !== renderSession.currentBlendMode)
{
renderSession.currentBlendMode = this.blendMode;
context.globalCompositeOperation = PIXI.blendModesCanvas[renderSession.currentBlendMode];
}
if (this._mask)
{
renderSession.maskManager.pushMask(this._mask, renderSession);
}
var resolution = renderSession.resolution;
var tx = (transform.tx * renderSession.resolution) + renderSession.shakeX;
var ty = (transform.ty * renderSession.resolution) + renderSession.shakeY;
context.setTransform(transform.a * resolution,
transform.b * resolution,
transform.c * resolution,
transform.d * resolution,
tx,
ty);
PIXI.CanvasGraphics.renderGraphics(this, context);
// simple render children!
for (var i = 0; i < this.children.length; i++)
{
this.children[i]._renderCanvas(renderSession);
}
if (this._mask)
{
renderSession.maskManager.popMask(renderSession);
}
}
};
/**
* Retrieves the bounds of the graphic shape as a rectangle object
*
* @method getBounds
* @return {Rectangle} the rectangular bounding area
*/
PIXI.Graphics.prototype.getBounds = function(matrix)
{
if (!this._currentBounds)
{
// Return an empty object if the item is a mask!
if (!this.renderable)
{
return PIXI.EmptyRectangle;
}
if (this.dirty)
{
this.updateLocalBounds();
this.webGLDirty = true;
this.cachedSpriteDirty = true;
this.dirty = false;
}
var bounds = this._localBounds;
var w0 = bounds.x;
var w1 = bounds.width + bounds.x;
var h0 = bounds.y;
var h1 = bounds.height + bounds.y;
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 x1 = a * w1 + c * h1 + tx;
var y1 = d * h1 + b * w1 + ty;
var x2 = a * w0 + c * h1 + tx;
var y2 = d * h1 + b * w0 + ty;
var x3 = a * w0 + c * h0 + tx;
var y3 = d * h0 + b * w0 + ty;
var x4 = a * w1 + c * h0 + tx;
var y4 = d * h0 + b * w1 + ty;
var maxX = x1;
var maxY = y1;
var minX = x1;
var minY = y1;
minX = x2 < minX ? x2 : minX;
minX = x3 < minX ? x3 : minX;
minX = x4 < minX ? x4 : minX;
minY = y2 < minY ? y2 : minY;
minY = y3 < minY ? y3 : minY;
minY = y4 < minY ? y4 : minY;
maxX = x2 > maxX ? x2 : maxX;
maxX = x3 > maxX ? x3 : maxX;
maxX = x4 > maxX ? x4 : maxX;
maxY = y2 > maxY ? y2 : maxY;
maxY = y3 > maxY ? y3 : maxY;
maxY = y4 > maxY ? y4 : maxY;
this._bounds.x = minX;
this._bounds.width = maxX - minX;
this._bounds.y = minY;
this._bounds.height = maxY - minY;
this._currentBounds = this._bounds;
}
return this._currentBounds;
};
/**
* Tests if a point is inside this graphics object
*
* @param point {Point} the point to test
* @return {boolean} the result of the test
*/
PIXI.Graphics.prototype.containsPoint = function( point )
{
this.worldTransform.applyInverse(point, tempPoint);
var graphicsData = this.graphicsData;
for (var i = 0; i < graphicsData.length; i++)
{
var data = graphicsData[i];
if (!data.fill)
{
continue;
}
// only deal with fills..
if (data.shape)
{
if (data.shape.contains(tempPoint.x, tempPoint.y))
{
return true;
}
}
}
return false;
};
/**
* Update the bounds of the object
*
* @method updateLocalBounds
*/
PIXI.Graphics.prototype.updateLocalBounds = function()
{
var minX = Infinity;
var maxX = -Infinity;
var minY = Infinity;
var maxY = -Infinity;
if (this.graphicsData.length)
{
var shape, points, x, y, w, h;
for (var i = 0; i < this.graphicsData.length; i++)
{
var data = this.graphicsData[i];
var type = data.type;
var lineWidth = data.lineWidth;
shape = data.shape;
if (type === PIXI.Graphics.RECT || type === PIXI.Graphics.RREC)
{
x = shape.x - lineWidth / 2;
y = shape.y - lineWidth / 2;
w = shape.width + lineWidth;
h = shape.height + lineWidth;
minX = x < minX ? x : minX;
maxX = x + w > maxX ? x + w : maxX;
minY = y < minY ? y : minY;
maxY = y + h > maxY ? y + h : maxY;
}
else if (type === PIXI.Graphics.CIRC)
{
x = shape.x;
y = shape.y;
w = shape.radius + lineWidth / 2;
h = shape.radius + lineWidth / 2;
minX = x - w < minX ? x - w : minX;
maxX = x + w > maxX ? x + w : maxX;
minY = y - h < minY ? y - h : minY;
maxY = y + h > maxY ? y + h : maxY;
}
else if (type === PIXI.Graphics.ELIP)
{
x = shape.x;
y = shape.y;
w = shape.width + lineWidth / 2;
h = shape.height + lineWidth / 2;
minX = x - w < minX ? x - w : minX;
maxX = x + w > maxX ? x + w : maxX;
minY = y - h < minY ? y - h : minY;
maxY = y + h > maxY ? y + h : maxY;
}
else
{
// POLY - assumes points are sequential, not Point objects
points = shape.points;
for (var j = 0; j < points.length; j++)
{
if (points[j] instanceof Phaser.Point)
{
x = points[j].x;
y = points[j].y;
}
else
{
x = points[j];
y = points[j + 1];
if (j < points.length - 1)
{
j++;
}
}
minX = x - lineWidth < minX ? x - lineWidth : minX;
maxX = x + lineWidth > maxX ? x + lineWidth : maxX;
minY = y - lineWidth < minY ? y - lineWidth : minY;
maxY = y + lineWidth > maxY ? y + lineWidth : maxY;
}
}
}
}
else
{
minX = 0;
maxX = 0;
minY = 0;
maxY = 0;
}
var padding = this.boundsPadding;
this._localBounds.x = minX - padding;
this._localBounds.width = (maxX - minX) + padding * 2;
this._localBounds.y = minY - padding;
this._localBounds.height = (maxY - minY) + padding * 2;
};
/**
* Generates the cached sprite when the sprite has cacheAsBitmap = true
*
* @method _generateCachedSprite
* @private
*/
PIXI.Graphics.prototype._generateCachedSprite = function()
{
var bounds = this.getLocalBounds();
if (!this._cachedSprite)
{
var canvasBuffer = new PIXI.CanvasBuffer(bounds.width, bounds.height);
var texture = PIXI.Texture.fromCanvas(canvasBuffer.canvas);
this._cachedSprite = new PIXI.Sprite(texture);
this._cachedSprite.buffer = canvasBuffer;
this._cachedSprite.worldTransform = this.worldTransform;
}
else
{
this._cachedSprite.buffer.resize(bounds.width, bounds.height);
}
// leverage the anchor to account for the offset of the element
this._cachedSprite.anchor.x = -(bounds.x / bounds.width);
this._cachedSprite.anchor.y = -(bounds.y / bounds.height);
// this._cachedSprite.buffer.context.save();
this._cachedSprite.buffer.context.translate(-bounds.x, -bounds.y);
// make sure we set the alpha of the graphics to 1 for the render..
this.worldAlpha = 1;
// now render the graphic..
PIXI.CanvasGraphics.renderGraphics(this, this._cachedSprite.buffer.context);
this._cachedSprite.alpha = this.alpha;
};
/**
* Updates texture size based on canvas size
*
* @method updateCachedSpriteTexture
* @private
*/
PIXI.Graphics.prototype.updateCachedSpriteTexture = function()
{
var cachedSprite = this._cachedSprite;
var texture = cachedSprite.texture;
var canvas = cachedSprite.buffer.canvas;
texture.baseTexture.width = canvas.width;
texture.baseTexture.height = canvas.height;
texture.crop.width = texture.frame.width = canvas.width;
texture.crop.height = texture.frame.height = canvas.height;
cachedSprite._width = canvas.width;
cachedSprite._height = canvas.height;
// update the dirty base textures
texture.baseTexture.dirty();
};
/**
* Destroys a previous cached sprite.
*
* @method destroyCachedSprite
*/
PIXI.Graphics.prototype.destroyCachedSprite = function()
{
this._cachedSprite.texture.destroy(true);
this._cachedSprite = null;
};
/**
* Draws the given shape to this Graphics object. Can be any of Circle, Rectangle, Ellipse, Line or Polygon.
*
* @method drawShape
* @param {Circle|Rectangle|Ellipse|Line|Polygon} shape The Shape object to draw.
* @return {GraphicsData} The generated GraphicsData object.
*/
PIXI.Graphics.prototype.drawShape = function(shape)
{
if (this.currentPath)
{
// check current path!
if (this.currentPath.shape.points.length <= 2)
{
this.graphicsData.pop();
}
}
this.currentPath = null;
// Handle mixed-type polygons
if (shape instanceof Phaser.Polygon)
{
shape = shape.clone();
shape.flatten();
}
var data = new PIXI.GraphicsData(this.lineWidth, this.lineColor, this.lineAlpha, this.fillColor, this.fillAlpha, this.filling, shape);
this.graphicsData.push(data);
if (data.type === PIXI.Graphics.POLY)
{
data.shape.closed = this.filling;
this.currentPath = data;
}
this.dirty = true;
return data;
};
/**
* When cacheAsBitmap is set to true the graphics object will be rendered as if it was a sprite.
* This is useful if your graphics element does not change often, as it will speed up the rendering of the object in exchange for taking up texture memory.
* It is also useful if you need the graphics object to be anti-aliased, because it will be rendered using canvas.
* This is not recommended if you are constantly redrawing the graphics element.
*
* @property cacheAsBitmap
* @type Boolean
* @default false
* @private
*/
Object.defineProperty(PIXI.Graphics.prototype, "cacheAsBitmap", {
get: function() {
return this._cacheAsBitmap;
},
set: function(value) {
this._cacheAsBitmap = value;
if (this._cacheAsBitmap)
{
this._generateCachedSprite();
}
else
{
this.destroyCachedSprite();
}
this.dirty = true;
this.webGLDirty = true;
}
});
/**
* A GraphicsData object.
*
* @class GraphicsData
* @constructor
PIXI.GraphicsData = function(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape)
{
this.lineWidth = lineWidth;
this.lineColor = lineColor;
this.lineAlpha = lineAlpha;
this._lineTint = lineColor;
this.fillColor = fillColor;
this.fillAlpha = fillAlpha;
this._fillTint = fillColor;
this.fill = fill;
this.shape = shape;
this.type = shape.type;
};
*/
/**
* A GraphicsData object.
*
* @class
* @memberof PIXI
* @param lineWidth {number} the width of the line to draw
* @param lineColor {number} the color of the line to draw
* @param lineAlpha {number} the alpha of the line to draw
* @param fillColor {number} the color of the fill
* @param fillAlpha {number} the alpha of the fill
* @param fill {boolean} whether or not the shape is filled with a colour
* @param shape {Circle|Rectangle|Ellipse|Line|Polygon} The shape object to draw.
*/
PIXI.GraphicsData = function(lineWidth, lineColor, lineAlpha, fillColor, fillAlpha, fill, shape) {
/*
* @member {number} the width of the line to draw
*/
this.lineWidth = lineWidth;
/*
* @member {number} the color of the line to draw
*/
this.lineColor = lineColor;
/*
* @member {number} the alpha of the line to draw
*/
this.lineAlpha = lineAlpha;
/*
* @member {number} cached tint of the line to draw
*/
this._lineTint = lineColor;
/*
* @member {number} the color of the fill
*/
this.fillColor = fillColor;
/*
* @member {number} the alpha of the fill
*/
this.fillAlpha = fillAlpha;
/*
* @member {number} cached tint of the fill
*/
this._fillTint = fillColor;
/*
* @member {boolean} whether or not the shape is filled with a color
*/
this.fill = fill;
/*
* @member {Circle|Rectangle|Ellipse|Line|Polygon} The shape object to draw.
*/
this.shape = shape;
/*
* @member {number} The type of the shape, see the Const.Shapes file for all the existing types,
*/
this.type = shape.type;
};
PIXI.GraphicsData.prototype.constructor = PIXI.GraphicsData;
/**
* Creates a new GraphicsData object with the same values as this one.
*
* @return {GraphicsData}
*/
PIXI.GraphicsData.prototype.clone = function() {
return new GraphicsData(
this.lineWidth,
this.lineColor,
this.lineAlpha,
this.fillColor,
this.fillAlpha,
this.fill,
this.shape
);
};
/*
PolyK library
url: http://polyk.ivank.net
Released under MIT licence.
Copyright (c) 2012 Ivan Kuckir
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
This is an amazing lib!
Slightly modified by Mat Groves (matgroves.com);
*/
/**
* Based on the Polyk library http://polyk.ivank.net released under MIT licence.
* This is an amazing lib!
* Slightly modified by Mat Groves (matgroves.com);
* @class PolyK
*/
PIXI.PolyK = {};
/**
* Triangulates shapes for webGL graphic fills.
*
* @method Triangulate
*/
PIXI.PolyK.Triangulate = function(p)
{
var sign = true;
var n = p.length >> 1;
if(n < 3) return [];
var tgs = [];
var avl = [];
for(var i = 0; i < n; i++) avl.push(i);
i = 0;
var al = n;
while(al > 3)
{
var i0 = avl[(i+0)%al];
var i1 = avl[(i+1)%al];
var i2 = avl[(i+2)%al];
var ax = p[2*i0], ay = p[2*i0+1];
var bx = p[2*i1], by = p[2*i1+1];
var cx = p[2*i2], cy = p[2*i2+1];
var earFound = false;
if(PIXI.PolyK._convex(ax, ay, bx, by, cx, cy, sign))
{
earFound = true;
for(var j = 0; j < al; j++)
{
var vi = avl[j];
if(vi === i0 || vi === i1 || vi === i2) continue;
if(PIXI.PolyK._PointInTriangle(p[2*vi], p[2*vi+1], ax, ay, bx, by, cx, cy)) {
earFound = false;
break;
}
}
}
if(earFound)
{
tgs.push(i0, i1, i2);
avl.splice((i+1)%al, 1);
al--;
i = 0;
}
else if(i++ > 3*al)
{
// need to flip flip reverse it!
// reset!
if(sign)
{
tgs = [];
avl = [];
for(i = 0; i < n; i++) avl.push(i);
i = 0;
al = n;
sign = false;
}
else
{
// window.console.log("PIXI Warning: shape too complex to fill");
return null;
}
}
}
tgs.push(avl[0], avl[1], avl[2]);
return tgs;
};
/**
* Checks whether a point is within a triangle
*
* @method _PointInTriangle
* @param px {Number} x coordinate of the point to test
* @param py {Number} y coordinate of the point to test
* @param ax {Number} x coordinate of the a point of the triangle
* @param ay {Number} y coordinate of the a point of the triangle
* @param bx {Number} x coordinate of the b point of the triangle
* @param by {Number} y coordinate of the b point of the triangle
* @param cx {Number} x coordinate of the c point of the triangle
* @param cy {Number} y coordinate of the c point of the triangle
* @private
* @return {Boolean}
*/
PIXI.PolyK._PointInTriangle = function(px, py, ax, ay, bx, by, cx, cy)
{
var v0x = cx-ax;
var v0y = cy-ay;
var v1x = bx-ax;
var v1y = by-ay;
var v2x = px-ax;
var v2y = py-ay;
var dot00 = v0x*v0x+v0y*v0y;
var dot01 = v0x*v1x+v0y*v1y;
var dot02 = v0x*v2x+v0y*v2y;
var dot11 = v1x*v1x+v1y*v1y;
var dot12 = v1x*v2x+v1y*v2y;
var invDenom = 1 / (dot00 * dot11 - dot01 * dot01);
var u = (dot11 * dot02 - dot01 * dot12) * invDenom;
var v = (dot00 * dot12 - dot01 * dot02) * invDenom;
// Check if point is in triangle
return (u >= 0) && (v >= 0) && (u + v < 1);
};
/**
* Checks whether a shape is convex
*
* @method _convex
* @private
* @return {Boolean}
*/
PIXI.PolyK._convex = function(ax, ay, bx, by, cx, cy, sign)
{
return ((ay-by)*(cx-bx) + (bx-ax)*(cy-by) >= 0) === sign;
};
/*
Copyright (c) 2016, Mapbox
Permission to use, copy, modify, and/or distribute this software for any purpose
with or without fee is hereby granted, provided that the above copyright notice
and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
THIS SOFTWARE.
*/
/**
* @class EarCut
*/
PIXI.EarCut = {};
PIXI.EarCut.Triangulate = function (data, holeIndices, dim) {
dim = dim || 2;
var hasHoles = holeIndices && holeIndices.length,
outerLen = hasHoles ? holeIndices[0] * dim : data.length,
outerNode = PIXI.EarCut.linkedList(data, 0, outerLen, dim, true),
triangles = [];
if (!outerNode) return triangles;
var minX, minY, maxX, maxY, x, y, size;
if (hasHoles) outerNode = PIXI.EarCut.eliminateHoles(data, holeIndices, outerNode, dim);
// if the shape is not too simple, we'll use z-order curve hash later; calculate polygon bbox
if (data.length > 80 * dim) {
minX = maxX = data[0];
minY = maxY = data[1];
for (var i = dim; i < outerLen; i += dim) {
x = data[i];
y = data[i + 1];
if (x < minX) minX = x;
if (y < minY) minY = y;
if (x > maxX) maxX = x;
if (y > maxY) maxY = y;
}
// minX, minY and size are later used to transform coords into integers for z-order calculation
size = Math.max(maxX - minX, maxY - minY);
}
PIXI.EarCut.earcutLinked(outerNode, triangles, dim, minX, minY, size);
return triangles;
}
// create a circular doubly linked list from polygon points in the specified winding order
PIXI.EarCut.linkedList = function (data, start, end, dim, clockwise) {
var sum = 0,
i, j, last;
// calculate original winding order of a polygon ring
for (i = start, j = end - dim; i < end; i += dim) {
sum += (data[j] - data[i]) * (data[i + 1] + data[j + 1]);
j = i;
}
// link points into circular doubly-linked list in the specified winding order
if (clockwise === (sum > 0)) {
for (i = start; i < end; i += dim) last = PIXI.EarCut.insertNode(i, data[i], data[i + 1], last);
} else {
for (i = end - dim; i >= start; i -= dim) last = PIXI.EarCut.insertNode(i, data[i], data[i + 1], last);
}
return last;
}
// eliminate colinear or duplicate points
PIXI.EarCut.filterPoints = function (start, end) {
if (!start) return start;
if (!end) end = start;
var p = start,
again;
do {
again = false;
if (!p.steiner && (PIXI.EarCut.equals(p, p.next) || PIXI.EarCut.area(p.prev, p, p.next) === 0)) {
PIXI.EarCut.removeNode(p);
p = end = p.prev;
if (p === p.next) return null;
again = true;
} else {
p = p.next;
}
} while (again || p !== end);
return end;
}
// main ear slicing loop which triangulates a polygon (given as a linked list)
PIXI.EarCut.earcutLinked = function (ear, triangles, dim, minX, minY, size, pass) {
if (!ear) return;
// interlink polygon nodes in z-order
if (!pass && size) PIXI.EarCut.indexCurve(ear, minX, minY, size);
var stop = ear,
prev, next;
// iterate through ears, slicing them one by one
while (ear.prev !== ear.next) {
prev = ear.prev;
next = ear.next;
if (size ? PIXI.EarCut.isEarHashed(ear, minX, minY, size) : PIXI.EarCut.isEar(ear)) {
// cut off the triangle
triangles.push(prev.i / dim);
triangles.push(ear.i / dim);
triangles.push(next.i / dim);
PIXI.EarCut.removeNode(ear);
// skipping the next vertice leads to less sliver triangles
ear = next.next;
stop = next.next;
continue;
}
ear = next;
// if we looped through the whole remaining polygon and can't find any more ears
if (ear === stop) {
// try filtering points and slicing again
if (!pass) {
PIXI.EarCut.earcutLinked(PIXI.EarCut.filterPoints(ear), triangles, dim, minX, minY, size, 1);
// if this didn't work, try curing all small self-intersections locally
} else if (pass === 1) {
ear = PIXI.EarCut.cureLocalIntersections(ear, triangles, dim);
PIXI.EarCut.earcutLinked(ear, triangles, dim, minX, minY, size, 2);
// as a last resort, try splitting the remaining polygon into two
} else if (pass === 2) {
PIXI.EarCut.splitEarcut(ear, triangles, dim, minX, minY, size);
}
break;
}
}
}
// check whether a polygon node forms a valid ear with adjacent nodes
PIXI.EarCut.isEar = function (ear) {
var a = ear.prev,
b = ear,
c = ear.next;
if (PIXI.EarCut.area(a, b, c) >= 0) return false; // reflex, can't be an ear
// now make sure we don't have other points inside the potential ear
var p = ear.next.next;
while (p !== ear.prev) {
if (PIXI.EarCut.pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y) &&
PIXI.EarCut.area(p.prev, p, p.next) >= 0) return false;
p = p.next;
}
return true;
}
PIXI.EarCut.isEarHashed = function (ear, minX, minY, size) {
var a = ear.prev,
b = ear,
c = ear.next;
if (PIXI.EarCut.area(a, b, c) >= 0) return false; // reflex, can't be an ear
// triangle bbox; min & max are calculated like this for speed
var minTX = a.x < b.x ? (a.x < c.x ? a.x : c.x) : (b.x < c.x ? b.x : c.x),
minTY = a.y < b.y ? (a.y < c.y ? a.y : c.y) : (b.y < c.y ? b.y : c.y),
maxTX = a.x > b.x ? (a.x > c.x ? a.x : c.x) : (b.x > c.x ? b.x : c.x),
maxTY = a.y > b.y ? (a.y > c.y ? a.y : c.y) : (b.y > c.y ? b.y : c.y);
// z-order range for the current triangle bbox;
var minZ = PIXI.EarCut.zOrder(minTX, minTY, minX, minY, size),
maxZ = PIXI.EarCut.zOrder(maxTX, maxTY, minX, minY, size);
// first look for points inside the triangle in increasing z-order
var p = ear.nextZ;
while (p && p.z <= maxZ) {
if (p !== ear.prev && p !== ear.next &&
PIXI.EarCut.pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y) &&
PIXI.EarCut.area(p.prev, p, p.next) >= 0) return false;
p = p.nextZ;
}
// then look for points in decreasing z-order
p = ear.prevZ;
while (p && p.z >= minZ) {
if (p !== ear.prev && p !== ear.next &&
PIXI.EarCut.pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y) &&
PIXI.EarCut.area(p.prev, p, p.next) >= 0) return false;
p = p.prevZ;
}
return true;
}
// go through all polygon nodes and cure small local self-intersections
PIXI.EarCut.cureLocalIntersections = function (start, triangles, dim) {
var p = start;
do {
var a = p.prev,
b = p.next.next;
// a self-intersection where edge (v[i-1],v[i]) intersects (v[i+1],v[i+2])
if (PIXI.EarCut.intersects(a, p, p.next, b) && PIXI.EarCut.locallyInside(a, b) && PIXI.EarCut.locallyInside(b, a)) {
triangles.push(a.i / dim);
triangles.push(p.i / dim);
triangles.push(b.i / dim);
// remove two nodes involved
PIXI.EarCut.removeNode(p);
PIXI.EarCut.removeNode(p.next);
p = start = b;
}
p = p.next;
} while (p !== start);
return p;
}
// try splitting polygon into two and triangulate them independently
PIXI.EarCut.splitEarcut = function (start, triangles, dim, minX, minY, size) {
// look for a valid diagonal that divides the polygon into two
var a = start;
do {
var b = a.next.next;
while (b !== a.prev) {
if (a.i !== b.i && PIXI.EarCut.isValidDiagonal(a, b)) {
// split the polygon in two by the diagonal
var c = PIXI.EarCut.splitPolygon(a, b);
// filter colinear points around the cuts
a = PIXI.EarCut.filterPoints(a, a.next);
c = PIXI.EarCut.filterPoints(c, c.next);
// run earcut on each half
PIXI.EarCut.earcutLinked(a, triangles, dim, minX, minY, size);
PIXI.EarCut.earcutLinked(c, triangles, dim, minX, minY, size);
return;
}
b = b.next;
}
a = a.next;
} while (a !== start);
}
// link every hole into the outer loop, producing a single-ring polygon without holes
PIXI.EarCut.eliminateHoles = function (data, holeIndices, outerNode, dim) {
var queue = [],
i, len, start, end, list;
for (i = 0, len = holeIndices.length; i < len; i++) {
start = holeIndices[i] * dim;
end = i < len - 1 ? holeIndices[i + 1] * dim : data.length;
list = PIXI.EarCut.linkedList(data, start, end, dim, false);
if (list === list.next) list.steiner = true;
queue.push(PIXI.EarCut.getLeftmost(list));
}
queue.sort(compareX)