phaser/v3/merge/gameobjects/rope/Rope.js
2016-11-23 00:17:46 +00:00

840 lines
22 KiB
JavaScript

/**
* @author Richard Davey <rich@photonstorm.com>
* @author Mat Groves (@Doormat23)
* @author Rovanion Luckey
* @copyright 2016 Photon Storm Ltd, Richard Davey
* @license {@link https://github.com/photonstorm/phaser/blob/master/license.txt|MIT License}
*/
/**
* A Rope is a Sprite that has a repeating texture.
*
* The texture will automatically wrap on the edges as it moves.
*
* Please note that Ropes cannot have an input handler.
*
* @class Phaser.GameObject.Rope
* @constructor
* @extends PIXI.DisplayObjectContainer
* @extends Phaser.Component.Core
* @extends Phaser.Component.Angle
* @extends Phaser.Component.Animation
* @extends Phaser.Component.AutoCull
* @extends Phaser.Component.Bounds
* @extends Phaser.Component.BringToTop
* @extends Phaser.Component.Crop
* @extends Phaser.Component.Delta
* @extends Phaser.Component.Destroy
* @extends Phaser.Component.FixedToCamera
* @extends Phaser.Component.InWorld
* @extends Phaser.Component.LifeSpan
* @extends Phaser.Component.LoadTexture
* @extends Phaser.Component.Overlap
* @extends Phaser.Component.PhysicsBody
* @extends Phaser.Component.Reset
* @extends Phaser.Component.ScaleMinMax
* @extends Phaser.Component.Smoothed
* @param {Phaser.Game} game - A reference to the currently running game.
* @param {number} x - The x coordinate (in world space) to position the Rope at.
* @param {number} y - The y coordinate (in world space) to position the Rope at.
* @param {string|Phaser.RenderTexture|Phaser.BitmapData|PIXI.Texture} key - This is the image or texture used by the Rope during rendering. It can be a string which is a reference to the Cache entry, or an instance of a RenderTexture or PIXI.Texture.
* @param {string|number} frame - If this Rope is using part of a sprite sheet or texture atlas you can specify the exact frame to use by giving a string or numeric index.
* @param {Array} points - An array of {Phaser.Point}.
*/
Phaser.GameObject.Rope = function (game, x, y, key, frame, points) {
this.points = [];
this.points = points;
this._hasUpdateAnimation = false;
this._updateAnimationCallback = null;
x = x || 0;
y = y || 0;
key = key || null;
frame = frame || null;
/**
* @property {number} type - The const type of this object.
* @readonly
*/
this.type = Phaser.ROPE;
this.points = points;
PIXI.DisplayObjectContainer.call(this);
this.texture = Phaser.Cache.DEFAULT;
// set up the main bits..
this.uvs = new Float32Array([0, 1,
1, 1,
1, 0,
0, 1]);
this.vertices = new Float32Array([0, 0,
100, 0,
100, 100,
0, 100]);
this.colors = new Float32Array([1, 1, 1, 1]);
this.indices = new Uint16Array([0, 1, 2, 3]);
if (points)
{
this.vertices = new Float32Array(points.length * 4);
this.uvs = new Float32Array(points.length * 4);
this.colors = new Float32Array(points.length * 2);
this.indices = new Uint16Array(points.length * 2);
}
/**
* Whether the strip is dirty or not
*
* @property dirty
* @type Boolean
*/
this.dirty = true;
/**
* Triangles in canvas mode are automatically antialiased, use this value to force triangles to overlap a bit with each other.
*
* @property canvasPadding
* @type Number
*/
this.canvasPadding = 0;
this.drawMode = Phaser.GameObject.Rope.TRIANGLE_STRIP;
Phaser.Component.Core.init.call(this, game, x, y, key, frame);
this.refresh();
};
Phaser.GameObject.Rope.prototype = Object.create(PIXI.DisplayObjectContainer.prototype);
Phaser.GameObject.Rope.prototype.constructor = Phaser.GameObject.Rope;
Phaser.Component.Core.install.call(Phaser.GameObject.Rope.prototype, [
'Angle',
'Animation',
'AutoCull',
'Bounds',
'BringToTop',
'Crop',
'Delta',
'Destroy',
'FixedToCamera',
'InWorld',
'LifeSpan',
'LoadTexture',
'Overlap',
'PhysicsBody',
'Reset',
'ScaleMinMax',
'Smoothed'
]);
Phaser.GameObject.Rope.prototype.preUpdatePhysics = Phaser.Component.PhysicsBody.preUpdate;
Phaser.GameObject.Rope.prototype.preUpdateLifeSpan = Phaser.Component.LifeSpan.preUpdate;
Phaser.GameObject.Rope.prototype.preUpdateInWorld = Phaser.Component.InWorld.preUpdate;
Phaser.GameObject.Rope.prototype.preUpdateCore = Phaser.Component.Core.preUpdate;
Phaser.GameObject.Rope.TRIANGLE_STRIP = 0;
Phaser.GameObject.Rope.TRIANGLES = 1;
/**
* Automatically called by World.preUpdate.
*
* @method Phaser.GameObject.Rope#preUpdate
* @memberof Phaser.GameObject.Rope
*/
Phaser.GameObject.Rope.prototype.preUpdate = function () {
if (!this.preUpdatePhysics() || !this.preUpdateLifeSpan() || !this.preUpdateInWorld())
{
return false;
}
return this.preUpdateCore();
};
/**
* Override and use this function in your own custom objects to handle any update requirements you may have.
*
* @method Phaser.GameObject.Rope#update
* @memberof Phaser.GameObject.Rope
*/
Phaser.GameObject.Rope.prototype.update = function() {
if (this._hasUpdateAnimation)
{
this.updateAnimation.call(this);
}
};
/**
* Resets the Rope. This places the Rope at the given x/y world coordinates and then
* sets alive, exists, visible and renderable all to true. Also resets the outOfBounds state.
* If the Rope has a physics body that too is reset.
*
* @method Phaser.GameObject.Rope#reset
* @memberof Phaser.GameObject.Rope
* @param {number} x - The x coordinate (in world space) to position the Sprite at.
* @param {number} y - The y coordinate (in world space) to position the Sprite at.
* @return {Phaser.GameObject.Rope} This instance.
*/
Phaser.GameObject.Rope.prototype.reset = function (x, y) {
Phaser.Component.Reset.prototype.reset.call(this, x, y);
return this;
};
/*
* Refreshes the rope texture and UV coordinates.
*
* @method Phaser.GameObject.Rope#refresh
* @memberof Phaser.GameObject.Rope
*/
Phaser.GameObject.Rope.prototype.refresh = function () {
var points = this.points;
if (points.length < 1)
{
return;
}
var uvs = this.uvs;
var indices = this.indices;
var colors = this.colors;
this.count -= 0.2;
uvs[0] = 0;
uvs[1] = 0;
uvs[2] = 0;
uvs[3] = 1;
colors[0] = 1;
colors[1] = 1;
indices[0] = 0;
indices[1] = 1;
var total = points.length;
var index;
var amount;
for (var i = 1; i < total; i++)
{
index = i * 4;
// time to do some smart drawing!
amount = i / (total - 1);
if (i % 2)
{
uvs[index] = amount;
uvs[index + 1] = 0;
uvs[index + 2] = amount;
uvs[index + 3] = 1;
}
else
{
uvs[index] = amount;
uvs[index + 1] = 0;
uvs[index + 2] = amount;
uvs[index + 3] = 1;
}
index = i * 2;
colors[index] = 1;
colors[index + 1] = 1;
index = i * 2;
indices[index] = index;
indices[index + 1] = index + 1;
}
};
/*
* Updates the Ropes transform ready for rendering.
*
* @method Phaser.GameObject.Rope#updateTransform
* @memberof Phaser.GameObject.Rope
*/
Phaser.GameObject.Rope.prototype.updateTransform = function () {
var points = this.points;
if (points.length < 1)
{
return;
}
var lastPoint = points[0];
var nextPoint;
var perp = { x:0, y:0 };
this.count -= 0.2;
var vertices = this.vertices;
var total = points.length;
var point;
var index;
var ratio;
var perpLength;
var num;
for (var i = 0; i < total; i++)
{
point = points[i];
index = i * 4;
if(i < points.length - 1)
{
nextPoint = points[i + 1];
}
else
{
nextPoint = point;
}
perp.y = -(nextPoint.x - lastPoint.x);
perp.x = nextPoint.y - lastPoint.y;
ratio = (1 - (i / (total - 1))) * 10;
if (ratio > 1)
{
ratio = 1;
}
perpLength = Math.sqrt((perp.x * perp.x) + (perp.y * perp.y));
num = this.texture.height / 2;
perp.x /= perpLength;
perp.y /= perpLength;
perp.x *= num;
perp.y *= num;
vertices[index] = point.x + perp.x;
vertices[index + 1] = point.y + perp.y;
vertices[index + 2] = point.x - perp.x;
vertices[index + 3] = point.y - perp.y;
lastPoint = point;
}
PIXI.DisplayObjectContainer.prototype.updateTransform.call(this);
};
/*
* Sets the Texture this Rope uses for rendering.
*
* @method Phaser.GameObject.Rope#setTexture
* @memberof Phaser.GameObject.Rope
* @param {Texture} texture - The texture that will be used.
*/
Phaser.GameObject.Rope.prototype.setTexture = function (texture) {
this.texture = texture;
};
/*
* Renders the Rope to WebGL.
*
* @private
* @method Phaser.GameObject.Rope#_renderWebGL
* @memberof Phaser.GameObject.Rope
*/
Phaser.GameObject.Rope.prototype._renderWebGL = function (renderSession) {
if (!this.visible || this.alpha <= 0)
{
return;
}
renderSession.spriteBatch.stop();
if (!this._vertexBuffer)
{
this._initWebGL(renderSession);
}
renderSession.shaderManager.setShader(renderSession.shaderManager.stripShader);
this._renderStrip(renderSession);
renderSession.spriteBatch.start();
};
/*
* Builds the Strip.
*
* @private
* @method Phaser.GameObject.Rope#_initWebGL
* @memberof Phaser.GameObject.Rope
*/
Phaser.GameObject.Rope.prototype._initWebGL = function (renderSession) {
// build the strip!
var gl = renderSession.gl;
this._vertexBuffer = gl.createBuffer();
this._indexBuffer = gl.createBuffer();
this._uvBuffer = gl.createBuffer();
this._colorBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, this._vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, this.vertices, gl.DYNAMIC_DRAW);
gl.bindBuffer(gl.ARRAY_BUFFER, this._uvBuffer);
gl.bufferData(gl.ARRAY_BUFFER, this.uvs, gl.STATIC_DRAW);
gl.bindBuffer(gl.ARRAY_BUFFER, this._colorBuffer);
gl.bufferData(gl.ARRAY_BUFFER, this.colors, gl.STATIC_DRAW);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this._indexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.indices, gl.STATIC_DRAW);
};
/*
* Renders the Strip to WebGL.
*
* @private
* @method Phaser.GameObject.Rope#_renderStrip
* @memberof Phaser.GameObject.Rope
*/
Phaser.GameObject.Rope.prototype._renderStrip = function (renderSession) {
var gl = renderSession.gl;
var projection = renderSession.projection;
var offset = renderSession.offset;
var shader = renderSession.shaderManager.stripShader;
var drawMode = (this.drawMode === Phaser.GameObject.Rope.TRIANGLE_STRIP) ? gl.TRIANGLE_STRIP : gl.TRIANGLES;
renderSession.blendModeManager.setBlendMode(this.blendMode);
// set uniforms
gl.uniformMatrix3fv(shader.translationMatrix, false, this.worldTransform.toArray(true));
gl.uniform2f(shader.projectionVector, projection.x, -projection.y);
gl.uniform2f(shader.offsetVector, -offset.x, -offset.y);
gl.uniform1f(shader.alpha, this.worldAlpha);
if (!this.dirty)
{
gl.bindBuffer(gl.ARRAY_BUFFER, this._vertexBuffer);
gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertices);
gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 0, 0);
// update the uvs
gl.bindBuffer(gl.ARRAY_BUFFER, this._uvBuffer);
gl.vertexAttribPointer(shader.aTextureCoord, 2, gl.FLOAT, false, 0, 0);
gl.activeTexture(gl.TEXTURE0);
// check if a texture is dirty..
if (this.texture.baseTexture._dirty[gl.id])
{
renderSession.renderer.updateTexture(this.texture.baseTexture);
}
else
{
// bind the current texture
gl.bindTexture(gl.TEXTURE_2D, this.texture.baseTexture._glTextures);
}
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this._indexBuffer);
}
else
{
this.dirty = false;
gl.bindBuffer(gl.ARRAY_BUFFER, this._vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, this.vertices, gl.STATIC_DRAW);
gl.vertexAttribPointer(shader.aVertexPosition, 2, gl.FLOAT, false, 0, 0);
// update the uvs
gl.bindBuffer(gl.ARRAY_BUFFER, this._uvBuffer);
gl.bufferData(gl.ARRAY_BUFFER, this.uvs, gl.STATIC_DRAW);
gl.vertexAttribPointer(shader.aTextureCoord, 2, gl.FLOAT, false, 0, 0);
gl.activeTexture(gl.TEXTURE0);
// check if a texture is dirty..
if (this.texture.baseTexture._dirty[gl.id])
{
renderSession.renderer.updateTexture(this.texture.baseTexture);
}
else
{
gl.bindTexture(gl.TEXTURE_2D, this.texture.baseTexture._glTextures);
}
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this._indexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.indices, gl.STATIC_DRAW);
}
gl.drawElements(drawMode, this.indices.length, gl.UNSIGNED_SHORT, 0);
};
/*
* Renders the Strip to Canvas.
*
* @private
* @method Phaser.GameObject.Rope#_renderCanvas
* @memberof Phaser.GameObject.Rope
*/
Phaser.GameObject.Rope.prototype._renderCanvas = function (renderSession) {
var context = renderSession.context;
var transform = this.worldTransform;
var tx = (transform.tx * renderSession.resolution) + renderSession.shakeX;
var ty = (transform.ty * renderSession.resolution) + renderSession.shakeY;
if (renderSession.roundPixels)
{
context.setTransform(transform.a, transform.b, transform.c, transform.d, tx | 0, ty | 0);
}
else
{
context.setTransform(transform.a, transform.b, transform.c, transform.d, tx, ty);
}
if (this.drawMode === Phaser.GameObject.Rope.TRIANGLE_STRIP)
{
this._renderCanvasTriangleStrip(context);
}
else
{
this._renderCanvasTriangles(context);
}
};
/*
* Renders a Triangle Strip to Canvas.
*
* @private
* @method Phaser.GameObject.Rope#_renderCanvasTriangleStrip
* @memberof Phaser.GameObject.Rope
*/
Phaser.GameObject.Rope.prototype._renderCanvasTriangleStrip = function (context) {
// draw triangles!!
var vertices = this.vertices;
var uvs = this.uvs;
var length = vertices.length / 2;
this.count++;
for (var i = 0; i < length - 2; i++)
{
var index = i * 2;
this._renderCanvasDrawTriangle(context, vertices, uvs, index, (index + 2), (index + 4));
}
};
/*
* Renders a Triangle to Canvas.
*
* @private
* @method Phaser.GameObject.Rope#_renderCanvasTriangles
* @memberof Phaser.GameObject.Rope
*/
Phaser.GameObject.Rope.prototype._renderCanvasTriangles = function (context) {
var vertices = this.vertices;
var uvs = this.uvs;
var indices = this.indices;
var length = indices.length;
this.count++;
for (var i = 0; i < length; i += 3)
{
var index0 = indices[i] * 2;
var index1 = indices[i + 1] * 2;
var index2 = indices[i + 2] * 2;
this._renderCanvasDrawTriangle(context, vertices, uvs, index0, index1, index2);
}
};
/*
* Renders a Triangle to Canvas.
*
* @private
* @method Phaser.GameObject.Rope#_renderCanvasDrawTriangle
* @memberof Phaser.GameObject.Rope
*/
Phaser.GameObject.Rope.prototype._renderCanvasDrawTriangle = function (context, vertices, uvs, index0, index1, index2) {
var textureSource = this.texture.baseTexture.source;
var textureWidth = this.texture.width;
var textureHeight = this.texture.height;
var x0 = vertices[index0];
var x1 = vertices[index1];
var x2 = vertices[index2];
var y0 = vertices[index0 + 1];
var y1 = vertices[index1 + 1];
var y2 = vertices[index2 + 1];
var u0 = uvs[index0] * textureWidth;
var u1 = uvs[index1] * textureWidth;
var u2 = uvs[index2] * textureWidth;
var v0 = uvs[index0 + 1] * textureHeight;
var v1 = uvs[index1 + 1] * textureHeight;
var v2 = uvs[index2 + 1] * textureHeight;
if (this.canvasPadding > 0)
{
var paddingX = this.canvasPadding / this.worldTransform.a;
var paddingY = this.canvasPadding / this.worldTransform.d;
var centerX = (x0 + x1 + x2) / 3;
var centerY = (y0 + y1 + y2) / 3;
var normX = x0 - centerX;
var normY = y0 - centerY;
var dist = Math.sqrt((normX * normX) + (normY * normY));
x0 = centerX + (normX / dist) * (dist + paddingX);
y0 = centerY + (normY / dist) * (dist + paddingY);
normX = x1 - centerX;
normY = y1 - centerY;
dist = Math.sqrt((normX * normX) + (normY * normY));
x1 = centerX + (normX / dist) * (dist + paddingX);
y1 = centerY + (normY / dist) * (dist + paddingY);
normX = x2 - centerX;
normY = y2 - centerY;
dist = Math.sqrt((normX * normX) + (normY * normY));
x2 = centerX + (normX / dist) * (dist + paddingX);
y2 = centerY + (normY / dist) * (dist + paddingY);
}
context.save();
context.beginPath();
context.moveTo(x0, y0);
context.lineTo(x1, y1);
context.lineTo(x2, y2);
context.closePath();
context.clip();
// Compute matrix transform
var delta = (u0 * v1) + (v0 * u2) + (u1 * v2) - (v1 * u2) - (v0 * u1) - (u0 * v2);
var deltaA = (x0 * v1) + (v0 * x2) + (x1 * v2) - (v1 * x2) - (v0 * x1) - (x0 * v2);
var deltaB = (u0 * x1) + (x0 * u2) + (u1 * x2) - (x1 * u2) - (x0 * u1) - (u0 * x2);
var deltaC = (u0 * v1 * x2) + (v0 * x1 * u2) + (x0 * u1 * v2) - (x0 * v1 * u2) - (v0 * u1 * x2) - (u0 * x1 * v2);
var deltaD = (y0 * v1) + (v0 * y2) + (y1 * v2) - (v1 * y2) - (v0 * y1) - (y0 * v2);
var deltaE = (u0 * y1) + (y0 * u2) + (u1 * y2) - (y1 * u2) - (y0 * u1) - (u0 * y2);
var deltaF = (u0 * v1 * y2) + (v0 * y1 * u2) + (y0 * u1 * v2) - (y0 * v1 * u2) - (v0 * u1 * y2) - (u0 * y1 * v2);
context.transform(
deltaA / delta,
deltaD / delta,
deltaB / delta,
deltaE / delta,
deltaC / delta,
deltaF / delta);
context.drawImage(textureSource, 0, 0);
context.restore();
};
/*
* Renders a flat strip.
*
* @method Phaser.GameObject.Rope#renderStripFlat
* @memberof Phaser.GameObject.Rope
*/
Phaser.GameObject.Rope.prototype.renderStripFlat = function (strip) {
var context = this.context;
var vertices = strip.vertices;
var length = vertices.length / 2;
this.count++;
context.beginPath();
for (var i = 1; i < length - 2; i++)
{
// draw some triangles!
var index = i * 2;
var x0 = vertices[index];
var x1 = vertices[index + 2];
var x2 = vertices[index + 4];
var y0 = vertices[index + 1];
var y1 = vertices[index + 3];
var y2 = vertices[index + 5];
context.moveTo(x0, y0);
context.lineTo(x1, y1);
context.lineTo(x2, y2);
}
context.fillStyle = '#FF0000';
context.fill();
context.closePath();
};
/*
* Returns the bounds of the mesh as a rectangle. The bounds calculation takes the worldTransform into account.
*
* @method Phaser.GameObject.Rope#getBounds
* @memberof Phaser.GameObject.Rope
* @param {Matrix} matrix - The transformation matrix of the Sprite.
* @return {Rectangle} The framing rectangle.
*/
Phaser.GameObject.Rope.prototype.getBounds = function (matrix) {
var worldTransform = matrix || this.worldTransform;
var a = worldTransform.a;
var b = worldTransform.b;
var c = worldTransform.c;
var d = worldTransform.d;
var tx = worldTransform.tx;
var ty = worldTransform.ty;
var maxX = -Infinity;
var maxY = -Infinity;
var minX = Infinity;
var minY = Infinity;
var vertices = this.vertices;
for (var i = 0; i < vertices.length; i += 2)
{
var rawX = vertices[i];
var rawY = vertices[i + 1];
var x = (a * rawX) + (c * rawY) + tx;
var y = (d * rawY) + (b * rawX) + ty;
minX = x < minX ? x : minX;
minY = y < minY ? y : minY;
maxX = x > maxX ? x : maxX;
maxY = y > maxY ? y : maxY;
}
if (minX === -Infinity || maxY === Infinity)
{
return Phaser.EmptyRectangle;
}
var bounds = this._bounds;
bounds.x = minX;
bounds.width = maxX - minX;
bounds.y = minY;
bounds.height = maxY - minY;
// Store a reference so that if this function gets called again in the render cycle we do not have to recalculate
this._currentBounds = bounds;
return bounds;
};
/**
* A Rope will call its updateAnimation function on each update loop if it has one.
*
* @name Phaser.GameObject.Rope#updateAnimation
* @property {function} updateAnimation - Set to a function if you'd like the rope to animate during the update phase. Set to false or null to remove it.
*/
Object.defineProperty(Phaser.GameObject.Rope.prototype, "updateAnimation", {
get: function () {
return this._updateAnimation;
},
set: function (value) {
if (value && typeof value === 'function')
{
this._hasUpdateAnimation = true;
this._updateAnimation = value;
}
else
{
this._hasUpdateAnimation = false;
this._updateAnimation = null;
}
}
});
/**
* The segments that make up the rope body as an array of Phaser.Rectangles
*
* @name Phaser.GameObject.Rope#segments
* @property {Phaser.Rectangles[]} updateAnimation - Returns an array of Phaser.Rectangles that represent the segments of the given rope
*/
Object.defineProperty(Phaser.GameObject.Rope.prototype, "segments", {
get: function () {
var segments = [];
var index, x1, y1, x2, y2, width, height, rect;
for (var i = 0; i < this.points.length; i++)
{
index = i * 4;
x1 = this.vertices[index] * this.scale.x;
y1 = this.vertices[index + 1] * this.scale.y;
x2 = this.vertices[index + 4] * this.scale.x;
y2 = this.vertices[index + 3] * this.scale.y;
width = Phaser.Math.difference(x1, x2);
height = Phaser.Math.difference(y1, y2);
x1 += this.world.x;
y1 += this.world.y;
rect = new Phaser.Rectangle(x1, y1, width, height);
segments.push(rect);
}
return segments;
}
});