phaser-arcade-slopes
Version:
A Phaser CE plugin that brings sloped tile collision handling to Phaser's Arcade Physics engine
1,525 lines (1,288 loc) • 154 kB
JavaScript
/**
* @author Chris Andrew <chris@hexus.io>
* @copyright 2016-2021 Chris Andrew
* @license MIT
*/
/**
* Arcade Slopes provides sloped tile functionality for tilemaps that use
* Phaser's Arcade physics engine.
*
* @class Phaser.Plugin.ArcadeSlopes
* @constructor
* @extends Phaser.Plugin
* @param {Phaser.Game} game - A reference to the game using this plugin.
* @param {any} parent - The object that owns this plugin, usually a Phaser.PluginManager.
* @param {integer} defaultSolver - The default collision solver type to use for sloped tiles.
*/
Phaser.Plugin.ArcadeSlopes = function (game, parent, defaultSolver) {
Phaser.Plugin.call(this, game, parent);
/**
* The collision solvers provided by the plugin.
*
* Maps solver constants to their respective instances.
*
* @property {object} solvers
*/
var solvers = {};
solvers[Phaser.Plugin.ArcadeSlopes.SAT] = new Phaser.Plugin.ArcadeSlopes.SatSolver();
/**
* The Arcade Slopes facade.
*
* @property {Phaser.Plugin.ArcadeSlopes.Facade} facade
*/
this.facade = new Phaser.Plugin.ArcadeSlopes.Facade(
new Phaser.Plugin.ArcadeSlopes.TileSlopeFactory(),
solvers,
defaultSolver || Phaser.Plugin.ArcadeSlopes.SAT
);
// Give the facade a reference to the plugin; this makes it easier to remove
// it at runtime
this.facade.plugin = this;
};
Phaser.Plugin.ArcadeSlopes.prototype = Object.create(Phaser.Plugin.prototype);
Phaser.Plugin.ArcadeSlopes.prototype.constructor = Phaser.Plugin.ArcadeSlopes;
/**
* The Arcade Slopes plugin version number.
*
* @constant
* @type {string}
*/
Phaser.Plugin.ArcadeSlopes.VERSION = '0.3.2';
/**
* The Separating Axis Theorem collision solver type.
*
* Uses the excellent SAT.js library.
*
* @constant
* @type {string}
*/
Phaser.Plugin.ArcadeSlopes.SAT = 'sat';
/**
* Initializes the plugin.
*
* @method Phaser.Plugin.ArcadeSlopes#init
*/
Phaser.Plugin.ArcadeSlopes.prototype.init = function () {
// Give the game an Arcade Slopes facade
this.game.slopes = this.game.slopes || this.facade;
// Keep a reference to the original Arcade.collideSpriteVsTilemapLayer method
this.originalCollideSpriteVsTilemapLayer = Phaser.Physics.Arcade.prototype.collideSpriteVsTilemapLayer;
// Replace the original method with the Arcade Slopes override, along with
// some extra methods that break down the functionality a little more
Phaser.Physics.Arcade.prototype.collideSpriteVsTile = Phaser.Plugin.ArcadeSlopes.Overrides.collideSpriteVsTile;
Phaser.Physics.Arcade.prototype.collideSpriteVsTiles = Phaser.Plugin.ArcadeSlopes.Overrides.collideSpriteVsTiles;
Phaser.Physics.Arcade.prototype.collideSpriteVsTilemapLayer = Phaser.Plugin.ArcadeSlopes.Overrides.collideSpriteVsTilemapLayer;
// Add some extra neighbour methods to the Tilemap class
Phaser.Tilemap.prototype.getTileTopLeft = Phaser.Plugin.ArcadeSlopes.Overrides.getTileTopLeft;
Phaser.Tilemap.prototype.getTileTopRight = Phaser.Plugin.ArcadeSlopes.Overrides.getTileTopRight;
Phaser.Tilemap.prototype.getTileBottomLeft = Phaser.Plugin.ArcadeSlopes.Overrides.getTileBottomLeft;
Phaser.Tilemap.prototype.getTileBottomRight = Phaser.Plugin.ArcadeSlopes.Overrides.getTileBottomRight;
// Keep a reference to the original TilemapLayer.renderDebug method
this.originalRenderDebug = Phaser.TilemapLayer.prototype.renderDebug;
// Add some overrides and helper methods to the TilemapLayer class
Phaser.TilemapLayer.prototype.getCollisionOffsetX = Phaser.Plugin.ArcadeSlopes.Overrides.getCollisionOffsetX;
Phaser.TilemapLayer.prototype.getCollisionOffsetY = Phaser.Plugin.ArcadeSlopes.Overrides.getCollisionOffsetY;
Phaser.TilemapLayer.prototype.renderDebug = Phaser.Plugin.ArcadeSlopes.Overrides.renderDebug;
};
/**
* Destroys the plugin and nulls its references. Restores any overriden methods.
*
* @method Phaser.Plugin.ArcadeSlopes#destroy
*/
Phaser.Plugin.ArcadeSlopes.prototype.destroy = function () {
// Null the game's reference to the facade
this.game.slopes = null;
// Restore the original collideSpriteVsTilemapLayer method and null the rest
Phaser.Physics.Arcade.prototype.collideSpriteVsTile = null;
Phaser.Physics.Arcade.prototype.collideSpriteVsTiles = null;
Phaser.Physics.Arcade.prototype.collideSpriteVsTilemapLayer = this.originalCollideSpriteVsTilemapLayer;
// Remove the extra neighbour methods from the Tilemap class
Phaser.Tilemap.prototype.getTileTopLeft = null;
Phaser.Tilemap.prototype.getTileTopRight = null;
Phaser.Tilemap.prototype.getTileBottomLeft = null;
Phaser.Tilemap.prototype.getTileBottomRight = null;
// Remove the overrides and helper methods from the TilemapLayer class
Phaser.TilemapLayer.prototype.getCollisionOffsetX = null;
Phaser.TilemapLayer.prototype.getCollisionOffsetY = null;
Phaser.TilemapLayer.prototype.renderDebug = this.originalRenderDebug;
// Call the parent destroy method
Phaser.Plugin.prototype.destroy.call(this);
};
/**
* @author Chris Andrew <chris@hexus.io>
* @copyright 2016-2021 Chris Andrew
* @license MIT
*/
/**
* A facade class to attach to a Phaser game.
*
* @class Phaser.Plugin.ArcadeSlopes.Facade
* @constructor
* @param {Phaser.Plugin.ArcadeSlopes.TileSlopeFactory} factory - A tile slope factory.
* @param {object} solvers - A set of collision solvers.
* @param {integer} defaultSolver - The default collision solver type to use for sloped tiles.
*/
Phaser.Plugin.ArcadeSlopes.Facade = function (factory, solvers, defaultSolver) {
/**
* A tile slope factory.
*
* @property {Phaser.Plugin.ArcadeSlopes.TileSlopeFactory} factory
*/
this.factory = factory;
/**
* A set of collision solvers.
*
* Maps solver constants to their respective instances.
*
* @property {object} solvers
*/
this.solvers = solvers;
/**
* The default collision solver type to use for sloped tiles.
*
* @property {string} defaultSolver
* @default
*/
this.defaultSolver = defaultSolver || Phaser.Plugin.ArcadeSlopes.SAT;
/**
* The plugin this facade belongs to.
*
* @property {Phaser.Plugin.ArcadeSlopes} plugin
*/
this.plugin = null;
};
/**
* Enable the physics body of the given object for sloped tile interaction.
*
* @method Phaser.Plugin.ArcadeSlopes.Facade#enable
* @param {Phaser.Sprite|Phaser.Group} object - The object to enable sloped tile physics for.
*/
Phaser.Plugin.ArcadeSlopes.Facade.prototype.enable = function (object) {
if (Array.isArray(object)) {
for (var i = 0; i < object.length; i++) {
this.enable(object[i]);
}
} else {
if (object instanceof Phaser.Group) {
this.enable(object.children);
} else {
if (object.hasOwnProperty('body')) {
this.enableBody(object.body);
}
if (object.hasOwnProperty('children') && object.children.length > 0) {
this.enable(object.children);
}
}
}
};
/**
* Enable the given physics body for sloped tile collisions.
*
* @method Phaser.Plugin.ArcadeSlopes.Facade#enableBody
* @param {Phaser.Physics.Arcade.Body} body - The physics body to enable.
*/
Phaser.Plugin.ArcadeSlopes.Facade.prototype.enableBody = function (body) {
// Create an SAT shape for the body
// TODO: Rename body.polygon to body.shape or body.slopes.shape
if (body.isCircle) {
body.polygon = new SAT.Circle(
new SAT.Vector(
body.x + body.halfWidth,
body.y + body.halfHeight
),
body.radius
);
} else {
body.polygon = new SAT.Box(
new SAT.Vector(body.x, body.y),
body.width * body.sprite.scale.x,
body.height * body.sprite.scale.y
).toPolygon();
}
// Attach a new set of properties that configure the body's interaction
// with sloped tiles, if they don't exist (TODO: Formalize as a class)
body.slopes = body.slopes || {
debug: false,
friction: new Phaser.Point(),
preferY: false,
pullUp: 0,
pullDown: 0,
pullLeft: 0,
pullRight: 0,
pullTopLeft: 0,
pullTopRight: 0,
pullBottomLeft: 0,
pullBottomRight: 0,
sat: {
response: null,
},
skipFriction: false,
tile: null,
velocity: new SAT.Vector()
};
};
/**
* Converts a layer of the given tilemap.
*
* Attaches Phaser.Plugin.ArcadeSlopes.TileSlope objects that are used to define
* how the tile should collide with a physics body.
*
* @method Phaser.Plugin.ArcadeSlopes.Facade#convertTilemap
* @param {Phaser.Tilemap} map - The map containing the layer to convert.
* @param {number|string|Phaser.TileMapLayer} layer - The layer of the map to convert.
* @param {string|object} slopeMap - A mapping type string, or a map of tilemap indexes to ArcadeSlope.TileSlope constants.
* @param {integer} index - An optional first tile index (firstgid).
* @return {Phaser.Tilemap} - The converted tilemap.
*/
Phaser.Plugin.ArcadeSlopes.Facade.prototype.convertTilemap = function (map, layer, slopeMap, index) {
return this.factory.convertTilemap(map, layer, slopeMap, index);
};
/**
* Converts a tilemap layer.
*
* @method Phaser.Plugin.ArcadeSlopes.Facade#convertTilemapLayer
* @param {Phaser.TilemapLayer} layer - The tilemap layer to convert.
* @param {string|object} slopeMap - A mapping type string, or a map of tilemap indexes to ArcadeSlope.TileSlope constants.
* @param {integer} index - An optional first tile index (firstgid).
* @return {Phaser.TilemapLayer} - The converted tilemap layer.
*/
Phaser.Plugin.ArcadeSlopes.Facade.prototype.convertTilemapLayer = function (layer, slopeMap, index) {
return this.factory.convertTilemapLayer(layer, slopeMap, index);
};
/**
* Collides a physics body against a tile.
*
* @method Phaser.Plugin.ArcadeSlopes.Facade#collide
* @param {integer} i - The tile index.
* @param {Phaser.Physics.Arcade.Body} body - The physics body.
* @param {Phaser.Tile} tile - The tile.
* @param {Phaser.TilemapLayer} tilemapLayer - The tilemap layer.
* @param {boolean} overlapOnly - Whether to only check for an overlap.
* @return {boolean} - Whether the body was separated.
*/
Phaser.Plugin.ArcadeSlopes.Facade.prototype.collide = function (i, body, tile, tilemapLayer, overlapOnly) {
return this.solvers.sat.collide(i, body, tile, tilemapLayer, overlapOnly);
};
/**
* Reset all the collision properties on a physics body.
*
* Resets body.touching, body.blocked, body.overlap*, body.slopes.sat.response.
*
* Leaves wasTouching alone.
*
* @method Phaser.Plugin.ArcadeSlopes.Facade#resetBodyFlags
* @param {Phaser.Physics.Arcade.Body} body - The physics body.
*/
Phaser.Plugin.ArcadeSlopes.Facade.prototype.resetCollision = function (body) {
body.touching.none = true;
body.touching.up = false;
body.touching.down = false;
body.touching.left = false;
body.touching.right = false;
body.blocked.none = true;
body.blocked.up = false;
body.blocked.down = false;
body.blocked.left = false;
body.blocked.right = false;
body.overlapX = 0;
body.overlapY = 0;
if (!body.slopes) {
return;
}
body.slopes.sat.response = null;
};
/**
* Whether to prefer Y axis separation in an attempt to prevent physics bodies
* from sliding down slopes when they are separated.
*
* Disabled by default. Only relevant in a game that uses gravity.
*
* @name Phaser.Plugin.ArcadeSlopes.Facade#preferY
* @property {boolean} preferY
*/
Object.defineProperty(Phaser.Plugin.ArcadeSlopes.Facade.prototype, 'preferY', {
get: function () {
return this.solvers.sat.options.preferY;
},
set: function (enabled) {
this.solvers.sat.options.preferY = !!enabled;
}
});
/**
* Whether to use heuristics to avoid collisions with the internal edges between
* connected tiles.
*
* Enabled by default. Relevant to platformers.
*
* @name Phaser.Plugin.ArcadeSlopes.Facade#heuristics
* @property {boolean} heuristics
*/
Object.defineProperty(Phaser.Plugin.ArcadeSlopes.Facade.prototype, 'heuristics', {
get: function () {
return this.solvers.sat.options.restrain;
},
set: function (enabled) {
this.solvers.sat.options.restrain = !!enabled;
}
});
/**
* @author Chris Andrew <chris@hexus.io>
* @copyright 2016-2021 Chris Andrew
* @license MIT
*/
/**
* A static class with override methods for Phaser's tilemap collisions and tile
* neighbour checks.
*
* @static
* @class Phaser.Plugin.ArcadeSlopes.Override
*/
Phaser.Plugin.ArcadeSlopes.Overrides = {};
/**
* Collide a sprite against a single tile.
*
* @method Phaser.Plugin.ArcadeSlopes.Overrides#collideSpriteVsTile
* @param {integer} i - The tile index.
* @param {Phaser.Sprite} sprite - The sprite to check.
* @param {Phaser.Tile} tile - The tile to check.
* @param {Phaser.TilemapLayer} tilemapLayer - The tilemap layer the tile belongs to.
* @param {function} [collideCallback] - An optional collision callback.
* @param {function} [processCallback] - An optional overlap processing callback.
* @param {object} [callbackContext] - The context in which to run the callbacks.
* @param {boolean} [overlapOnly] - Whether to only check for an overlap.
* @return {boolean} - Whether a collision occurred.
*/
Phaser.Plugin.ArcadeSlopes.Overrides.collideSpriteVsTile = function (i, sprite, tile, tilemapLayer, collideCallback, processCallback, callbackContext, overlapOnly) {
if (!sprite.body || !tile || !tilemapLayer) {
return false;
}
if (tile.hasOwnProperty('slope')) {
if (this.game.slopes.collide(i, sprite.body, tile, tilemapLayer, overlapOnly)) {
this._total++;
if (collideCallback) {
collideCallback.call(callbackContext, sprite, tile);
}
return true;
}
} else if (this.separateTile(i, sprite.body, tile, tilemapLayer, overlapOnly)) {
this._total++;
if (collideCallback) {
collideCallback.call(callbackContext, sprite, tile);
}
return true;
}
return false;
};
/**
* Collide a sprite against a set of tiles.
*
* @method Phaser.Plugin.ArcadeSlopes.Overrides#collideSpriteVsTiles
* @param {Phaser.Sprite} sprite - The sprite to check.
* @param {Phaser.Tile[]} tiles - The tiles to check.
* @param {Phaser.TilemapLayer} tilemapLayer - The tilemap layer the tiles belong to.
* @param {function} [collideCallback] - An optional collision callback.
* @param {function} [processCallback] - An optional overlap processing callback.
* @param {object} [callbackContext] - The context in which to run the callbacks.
* @param {boolean} [overlapOnly] - Whether to only check for an overlap.
* @return {boolean} - Whether a collision occurred.
*/
Phaser.Plugin.ArcadeSlopes.Overrides.collideSpriteVsTiles = function (sprite, tiles, tilemapLayer, collideCallback, processCallback, callbackContext, overlapOnly) {
if (!sprite.body || !tiles || !tiles.length || !tilemapLayer) {
return false;
}
var collided = false;
for (var i = 0; i < tiles.length; i++) {
if (processCallback) {
if (processCallback.call(callbackContext, sprite, tiles[i])) {
collided = this.collideSpriteVsTile(i, sprite, tiles[i], tilemapLayer, collideCallback, processCallback, callbackContext, overlapOnly) || collided;
}
} else {
collided = this.collideSpriteVsTile(i, sprite, tiles[i], tilemapLayer, collideCallback, processCallback, callbackContext, overlapOnly) || collided;
}
}
return collided;
};
/**
* Collide a sprite against a tile map layer.
*
* This is used to override Phaser.Physics.Arcade.collideSpriteVsTilemapLayer().
*
* @override Phaser.Physics.Arcade#collideSpriteVsTilemapLayer
* @method Phaser.Plugin.ArcadeSlopes.Overrides#collideSpriteVsTilemapLayer
* @param {Phaser.Sprite} sprite - The sprite to check.
* @param {Phaser.TilemapLayer} tilemapLayer - The tilemap layer to check.
* @param {function} collideCallback - An optional collision callback.
* @param {function} processCallback - An optional overlap processing callback.
* @param {object} callbackContext - The context in which to run the callbacks.
* @param {boolean} overlapOnly - Whether to only check for an overlap.
* @return {boolean} - Whether a collision occurred.
*/
Phaser.Plugin.ArcadeSlopes.Overrides.collideSpriteVsTilemapLayer = function (sprite, tilemapLayer, collideCallback, processCallback, callbackContext, overlapOnly) {
if (!sprite.body || !tilemapLayer) {
return false;
}
var tiles = tilemapLayer.getTiles(
sprite.body.position.x - sprite.body.tilePadding.x - tilemapLayer.getCollisionOffsetX(),
sprite.body.position.y - sprite.body.tilePadding.y - tilemapLayer.getCollisionOffsetY(),
sprite.body.width + sprite.body.tilePadding.x,
sprite.body.height + sprite.body.tilePadding.y,
true,
false
);
if (tiles.length === 0) {
return false;
}
// TODO: Sort by distance from body center to tile center?
var collided = this.collideSpriteVsTiles(sprite, tiles, tilemapLayer, collideCallback, processCallback, callbackContext, overlapOnly);
return collided;
};
/**
* Gets the tile to the top left of the coordinates given.
*
* @method Phaser.Plugin.ArcadeSlopes.Overrides#getTileTopLeft
* @param {integer} layer - The index of the layer to read the tile from.
* @param {integer} x - The X coordinate, in tiles, to get the tile from.
* @param {integer} y - The Y coordinate, in tiles, to get the tile from.
* @return {Phaser.Tile} - The tile found.
*/
Phaser.Plugin.ArcadeSlopes.Overrides.getTileTopLeft = function(layer, x, y) {
if (x > 0 && y > 0) {
return this.layers[layer].data[y - 1][x - 1];
}
return null;
};
/**
* Gets the tile to the top right of the coordinates given.
*
* @method Phaser.Plugin.ArcadeSlopes.Overrides#getTileTopRight
* @param {integer} layer - The index of the layer to read the tile from.
* @param {integer} x - The X coordinate, in tiles, to get the tile from.
* @param {integer} y - The Y coordinate, in tiles, to get the tile from.
* @return {Phaser.Tile} - The tile found.
*/
Phaser.Plugin.ArcadeSlopes.Overrides.getTileTopRight = function(layer, x, y) {
if (x < this.layers[layer].width - 1 && y > 0) {
return this.layers[layer].data[y - 1][x + 1];
}
return null;
};
/**
* Gets the tile to the bottom left of the coordinates given.
*
* @method Phaser.Plugin.ArcadeSlopes.Overrides#getTileBottomLeft
* @param {integer} layer - The index of the layer to read the tile from.
* @param {integer} x - The X coordinate, in tiles, to get the tile from.
* @param {integer} y - The Y coordinate, in tiles, to get the tile from.
* @return {Phaser.Tile} - The tile found.
*/
Phaser.Plugin.ArcadeSlopes.Overrides.getTileBottomLeft = function(layer, x, y) {
if (x > 0 && y < this.layers[layer].height - 1) {
return this.layers[layer].data[y + 1][x - 1];
}
return null;
};
/**
* Gets the tile to the bottom right of the coordinates given.
*
* @method Phaser.Plugin.ArcadeSlopes.Overrides#getTileBottomRight
* @param {integer} layer - The index of the layer to read the tile from.
* @param {integer} x - The X coordinate, in tiles, to get the tile from.
* @param {integer} y - The Y coordinate, in tiles, to get the tile from.
* @return {Phaser.Tile} - The tile found.
*/
Phaser.Plugin.ArcadeSlopes.Overrides.getTileBottomRight = function(layer, x, y) {
if (x < this.layers[layer].width - 1 && y < this.layers[layer].height - 1) {
return this.layers[layer].data[y + 1][x + 1];
}
return null;
};
/**
* Get the X axis collision offset for the tilemap layer.
*
* @method Phaser.Plugin.ArcadeSlopes.Overrides#getCollisionOffsetY
* @return {number}
*/
Phaser.Plugin.ArcadeSlopes.Overrides.getCollisionOffsetX = function () {
if (this.getTileOffsetX) {
return this.getTileOffsetX();
}
return !this.fixedToCamera ? this.position.x : 0;
};
/**
* Get the Y axis collision offset for the tilemap layer.
*
* @method Phaser.Plugin.ArcadeSlopes.Overrides#getCollisionOffsetY
* @return {number}
*/
Phaser.Plugin.ArcadeSlopes.Overrides.getCollisionOffsetY = function () {
if (this.getTileOffsetY) {
return this.getTileOffsetY();
}
return !this.fixedToCamera ? this.position.y : 0;
};
/**
* Renders a tilemap debug overlay on-top of the canvas.
*
* Called automatically by render when `debug` is true.
*
* See `debugSettings` for assorted configuration options.
*
* This override renders extra information regarding Arcade Slopes collisions.
*
* @method Phaser.Plugin.ArcadeSlopes.Overrides#renderDebug
* @private
*/
Phaser.Plugin.ArcadeSlopes.Overrides.renderDebug = function () {
var scrollX = this._mc.scrollX;
var scrollY = this._mc.scrollY;
var context = this.context;
var renderW = this.canvas.width;
var renderH = this.canvas.height;
var scaleX = this.tileScale ? this.tileScale.x : 1.0 / this.scale.x;
var scaleY = this.tileScale ? this.tileScale.y : 1.0 / this.scale.y;
var width = this.layer.width;
var height = this.layer.height;
var tw = this._mc.tileWidth * scaleX; // Tile width
var th = this._mc.tileHeight * scaleY; // Tile height
var htw = tw / 2; // Half-tile width
var hth = th / 2; // Half-tile height
var qtw = tw / 4; // Quarter-tile width
var qth = th / 4; // Quarter-tile height
var cw = this._mc.cw * scaleX;
var ch = this._mc.ch * scaleY;
var m = this._mc.edgeMidpoint;
var left = Math.floor(scrollX / tw);
var right = Math.floor((renderW - 1 + scrollX) / tw);
var top = Math.floor(scrollY / th);
var bottom = Math.floor((renderH - 1 + scrollY) / th);
if (!this._wrap)
{
if (left <= right) {
left = Math.max(0, left);
right = Math.min(width - 1, right);
}
if (top <= bottom) {
top = Math.max(0, top);
bottom = Math.min(height - 1, bottom);
}
}
var baseX = (left * tw) - scrollX;
var baseY = (top * th) - scrollY;
var normStartX = (left + ((1 << 20) * width)) % width;
var normStartY = (top + ((1 << 20) * height)) % height;
var tx, ty, x, y, xmax, ymax, polygon, i, j, a, b, norm, gx, gy, line;
for (y = normStartY, ymax = bottom - top, ty = baseY; ymax >= 0; y++, ymax--, ty += th) {
if (y >= height) {
y -= height;
}
var row = this.layer.data[y];
for (x = normStartX, xmax = right - left, tx = baseX; xmax >= 0; x++, xmax--, tx += tw) {
if (x >= width) {
x -= width;
}
var tile = row[x];
if (!tile || tile.index < 0 || !tile.collides) {
continue;
}
if (this.debugSettings.collidingTileOverfill) {
context.fillStyle = this.debugSettings.collidingTileOverfill;
context.fillRect(tx, ty, cw, ch);
}
if (this.debugSettings.facingEdgeStroke) {
context.beginPath();
context.lineWidth = 1;
context.strokeStyle = this.debugSettings.facingEdgeStroke;
if (tile.faceTop) {
context.moveTo(tx, ty);
context.lineTo(tx + cw, ty);
}
if (tile.faceBottom) {
context.moveTo(tx, ty + ch);
context.lineTo(tx + cw, ty + ch);
}
if (tile.faceLeft) {
context.moveTo(tx, ty);
context.lineTo(tx, ty + ch);
}
if (tile.faceRight) {
context.moveTo(tx + cw, ty);
context.lineTo(tx + cw, ty + ch);
}
context.closePath();
context.stroke();
// Render the tile slope polygons
if (tile.slope) {
// Fill polygons and stroke their edges
if (this.debugSettings.slopeEdgeStroke || this.debugSettings.slopeFill) {
context.beginPath();
context.lineWidth = 1;
polygon = tile.slope.polygon;
// Move to the first vertex
context.moveTo(tx + polygon.points[0].x * scaleX, ty + polygon.points[0].y * scaleY);
// Draw a path through all vertices
for (i = 0; i < polygon.points.length; i++) {
j = (i + 1) % polygon.points.length;
context.lineTo(tx + polygon.points[j].x * scaleX, ty + polygon.points[j].y * scaleY);
}
context.closePath();
if (this.debugSettings.slopeEdgeStroke) {
context.strokeStyle = this.debugSettings.slopeEdgeStroke;
context.stroke();
}
if (this.debugSettings.slopeFill) {
context.fillStyle = this.debugSettings.slopeFill;
context.fill();
}
}
// Stroke the colliding edges and edge normals
if (this.debugSettings.slopeCollidingEdgeStroke) {
// Colliding edges
context.beginPath();
context.lineWidth = this.debugSettings.slopeCollidingEdgeStrokeWidth || 1;
context.strokeStyle = this.debugSettings.slopeCollidingEdgeStroke;
polygon = tile.slope.polygon;
for (i = 0; i < polygon.points.length; i++) {
// Skip the edges with ignored normals
if (polygon.normals[i].ignore) {
continue;
}
j = (i + 1) % polygon.points.length;
context.moveTo(tx + polygon.points[i].x * scaleX, ty + polygon.points[i].y * scaleY);
context.lineTo(tx + polygon.points[j].x * scaleX, ty + polygon.points[j].y * scaleY);
}
context.closePath();
context.stroke();
// Edge normals
for (i = 0; i < polygon.points.length; i++) {
context.beginPath();
if (polygon.normals[i].ignore) {
context.lineWidth = this.debugSettings.slopeNormalStrokeWidth;
context.strokeStyle = this.debugSettings.slopeNormalStroke;
} else {
context.lineWidth = this.debugSettings.slopeCollidingNormalStrokeWidth;
context.strokeStyle = this.debugSettings.slopeCollidingNormalStroke;
}
j = (i + 1) % polygon.points.length;
a = polygon.points[i];
b = polygon.points[j];
norm = polygon.normals[i];
// Midpoint of the edge
m.x = (a.x + b.x) / 2;
m.y = (a.y + b.y) / 2;
// Draw from the midpoint outwards using the normal
context.moveTo(tx + m.x * scaleX, ty + m.y * scaleY);
context.lineTo(tx + m.x * scaleX + norm.x * qtw, ty + m.y * scaleY + norm.y * qth);
context.closePath();
context.stroke();
}
// Ignormals
if (tile.slope.ignormals) {
for (i = 0; i < tile.slope.ignormals.length; i++) {
context.beginPath();
context.lineWidth = 1;
context.strokeStyle = 'rgba(255, 0, 0, 1)';
gx = tile.slope.ignormals[i].x;
gy = tile.slope.ignormals[i].y;
context.moveTo(tx + htw, ty + hth);
context.lineTo(tx + htw + gx * qtw, ty + hth + gy * qth);
context.closePath();
context.stroke();
}
}
}
// Slope line segments
if (this.debugSettings.slopeLineStroke && tile.slope.line) {
line = tile.slope.line;
context.beginPath();
context.lineWidth = this.debugSettings.slopeLineWidth || 2;
context.strokeStyle = this.debugSettings.slopeLineStroke;
context.moveTo(line.start.x - scrollX, line.start.y - scrollY);
context.lineTo(line.end.x - scrollX, line.end.y - scrollY);
context.closePath();
context.stroke();
}
}
}
}
}
};
/**
* @author Chris Andrew <chris@hexus.io>
* @copyright 2016-2021 Chris Andrew
* @license MIT
*/
/**
* Solves tile collisions using the Separating Axis Theorem.
*
* @class Phaser.Plugin.ArcadeSlopes.SatSolver
* @constructor
* @param {object} options - Options for the SAT solver.
*/
Phaser.Plugin.ArcadeSlopes.SatSolver = function (options) {
/**
* Options for the SAT solver.
*
* @property {object} options
*/
this.options = Phaser.Utils.mixin(options || {}, {
// Whether to store debug data with all encountered physics bodies
debug: false,
// Whether to prefer the minimum Y offset over the smallest separation
preferY: false
});
/**
* A pool of arrays to use for calculations.
*
* @property {Array[]} arrayPool
*/
this.arrayPool = [];
for (var i = 0; i < 10; i++) {
this.arrayPool.push([]);
}
/**
* A pool of vectors to use for calculations.
*
* @property {SAT.Vector[]} vectorPool
*/
this.vectorPool = [];
for (i = 0; i < 20; i++) {
this.vectorPool.push(new SAT.Vector());
}
/**
* A pool of responses to use for collision tests.
*
* @property {SAT.Response[]} responsePool
*/
this.responsePool = [];
for (i = 0; i < 20; i++) {
this.responsePool.push(new SAT.Response());
}
};
/**
* Prepare the given SAT response by inverting the overlap vectors.
*
* @static
* @method Phaser.Plugin.ArcadeSlopes.SatSolver#prepareResponse
* @param {SAT.Response} response
* @return {SAT.Response}
*/
Phaser.Plugin.ArcadeSlopes.SatSolver.prepareResponse = function (response) {
// Invert our overlap vectors so that we have them facing outwards
response.overlapV.scale(-1);
response.overlapN.scale(-1);
return response;
};
/**
* Reset the given SAT response's properties to their default values.
*
* @static
* @method Phaser.Plugin.ArcadeSlopes.SatSolver#resetResponse
* @param {SAT.Response} response
* @return {SAT.Response}
*/
Phaser.Plugin.ArcadeSlopes.SatSolver.resetResponse = function (response) {
response.overlapN.x = 0;
response.overlapN.y = 0;
response.overlapV.x = 0;
response.overlapV.y = 0;
response.clear();
return response;
};
/**
* Copy the values of one SAT response to another.
*
* @static
* @method Phaser.Plugin.ArcadeSlopes.SatSolver#copyResponse
* @param {SAT.Response} a - The source response.
* @param {SAT.Response} b - The target response.
* @return {SAT.Response}
*/
Phaser.Plugin.ArcadeSlopes.SatSolver.copyResponse = function (a, b) {
b.a = a.a;
b.b = a.b;
b.aInB = a.aInB;
b.bInA = a.bInA;
b.overlap = a.overlap;
b.overlapN.copy(a.overlapN);
b.overlapV.copy(a.overlapV);
return b;
};
/**
* Calculate the minimum X offset given an overlap vector.
*
* @static
* @method Phaser.Plugin.ArcadeSlopes.SatSolver#minimumOffsetX
* @param {SAT.Vector} vector - The overlap vector.
* @return {integer}
*/
Phaser.Plugin.ArcadeSlopes.SatSolver.minimumOffsetX = function (vector) {
return ((vector.y * vector.y) / vector.x) + vector.x;
};
/**
* Calculate the minimum Y offset given an overlap vector.
*
* @static
* @method Phaser.Plugin.ArcadeSlopes.SatSolver#minimumOffsetY
* @param {SAT.Vector} vector - The overlap vector.
* @return {integer}
*/
Phaser.Plugin.ArcadeSlopes.SatSolver.minimumOffsetY = function (vector) {
return ((vector.x * vector.x) / vector.y) + vector.y;
};
/**
* Determine whether the given body is moving against the overlap vector of the
* given response on the Y axis.
*
* @static
* @method Phaser.Plugin.ArcadeSlopes.SatSolver#movingAgainstY
* @param {Phaser.Physics.Arcade.Body} body - The physics body.
* @param {SAT.Response} response - The SAT response.
* @return {boolean} - Whether the body is moving against the overlap vector.
*/
Phaser.Plugin.ArcadeSlopes.SatSolver.movingAgainstY = function (body, response) {
return (response.overlapV.y < 0 && body.velocity.y > 0) || (response.overlapV.y > 0 && body.velocity.y < 0);
};
// TODO: shouldPreferX()
/**
* Determine whether a body should be separated on the Y axis only, given an SAT
* response.
*
* Returns true if options.preferY is true, the overlap vector is non-zero
* for each axis and the body is moving against the overlap vector.
*
* TODO: Adapt for circle bodies, somehow. Disable for now?
* TODO: Would be amazing to check to ensure that there are no other surrounding collisions.
*
* @method Phaser.Plugin.ArcadeSlopes.SatSolver#shouldPreferY
* @param {Phaser.Physics.Arcade.Body} body - The physics body.
* @param {SAT.Response} response - The SAT response.
* @return {boolean} - Whether to separate on the Y axis only.
*/
Phaser.Plugin.ArcadeSlopes.SatSolver.prototype.shouldPreferY = function (body, response) {
return (this.options.preferY || body.slopes.preferY) && // Enabled globally or on the body
response.overlapV.y !== 0 && response.overlapV.x !== 0 && // There's an overlap on both axes
Phaser.Plugin.ArcadeSlopes.SatSolver.movingAgainstY(body, response); // And we're moving into the shape
};
/**
* Separate a body from a tile using the given SAT response.
*
* @method Phaser.Plugin.ArcadeSlopes.SatSolver#separate
* @param {Phaser.Physics.Arcade.Body} body - The physics body.
* @param {Phaser.Tile} tile - The tile.
* @param {SAT.Response} response - The SAT response.
* @param {boolean} force - Whether to force separation.
* @return {boolean} - Whether the body was separated.
*/
Phaser.Plugin.ArcadeSlopes.SatSolver.prototype.separate = function (body, tile, response, force) {
// Test whether we need to separate from the tile by checking its edge
// properties and any separation constraints
if (!force && !this.shouldSeparate(tile.index, body, tile, response)) {
return false;
}
// Run any custom tile callbacks, with local callbacks taking priority over
// layer level callbacks
if (tile.collisionCallback && !tile.collisionCallback.call(tile.collisionCallbackContext, body.sprite, tile)) {
return false;
} else if (tile.layer.callbacks[tile.index] && !tile.layer.callbacks[tile.index].callback.call(tile.layer.callbacks[tile.index].callbackContext, body.sprite, tile)) {
return false;
}
// Separate the body from the tile, using the minimum Y offset if preferred
if (this.shouldPreferY(body, response)) {
body.position.y += Phaser.Plugin.ArcadeSlopes.SatSolver.minimumOffsetY(response.overlapV);
} else {
body.position.x += response.overlapV.x;
body.position.y += response.overlapV.y;
}
return true;
};
/**
* Apply velocity changes (friction and bounce) to a body given a tile and
* SAT collision response.
*
* TODO: Optimize by pooling bounce and friction vectors.
*
* @method Phaser.Plugin.ArcadeSlopes.SatSolver#applyVelocity
* @param {Phaser.Physics.Arcade.Body} body - The physics body.
* @param {Phaser.Tile} tile - The tile.
* @param {SAT.Response} response - The SAT response.
*/
Phaser.Plugin.ArcadeSlopes.SatSolver.prototype.applyVelocity = function (body, tile, response) {
// Project our velocity onto the overlap normal for the bounce vector (Vn)
var bounce = this.vectorPool.pop().copy(body.slopes.velocity).projectN(response.overlapN);
// Then work out the surface vector (Vt)
var friction = this.vectorPool.pop().copy(body.slopes.velocity).sub(bounce);
// Apply bounce coefficients
bounce.x = bounce.x * (-body.bounce.x);
bounce.y = bounce.y * (-body.bounce.y);
// Apply friction coefficients
friction.x = friction.x * (1 - body.slopes.friction.x - tile.slope.friction.x);
friction.y = friction.y * (1 - body.slopes.friction.y - tile.slope.friction.y);
// Now we can get our new velocity by adding the bounce and friction vectors
body.velocity.x = bounce.x + friction.x;
body.velocity.y = bounce.y + friction.y;
// Process collision pulling
this.pull(body, response);
// Recycle the vectors we used for bounce and friction
this.vectorPool.push(bounce, friction);
};
/**
* Update the position and velocity values of the slopes body.
*
* @method Phaser.Plugin.ArcadeSlopes.SatSolver#updateValues
* @param {Phaser.Physics.Arcade.Body} body - The physics body.
*/
Phaser.Plugin.ArcadeSlopes.SatSolver.prototype.updateValues = function (body) {
// Update the body polygon position
body.polygon.pos.x = body.x;
body.polygon.pos.y = body.y;
// Update the body's velocity vector
body.slopes.velocity.x = body.velocity.x;
body.slopes.velocity.y = body.velocity.y;
};
/**
* Update the flags of a physics body using a given SAT response.
*
* @method Phaser.Plugin.ArcadeSlopes.SatSolver#updateFlags
* @param {Phaser.Physics.Arcade.Body} body - The physics body.
* @param {SAT.Response} response - The SAT response.
*/
Phaser.Plugin.ArcadeSlopes.SatSolver.prototype.updateFlags = function (body, response) {
// Set the touching values
body.touching.up = body.touching.up || response.overlapV.y > 0;
body.touching.down = body.touching.down || response.overlapV.y < 0;
body.touching.left = body.touching.left || response.overlapV.x > 0;
body.touching.right = body.touching.right || response.overlapV.x < 0;
body.touching.none = !body.touching.up && !body.touching.down && !body.touching.left && !body.touching.right;
// Set the blocked values
body.blocked.up = body.blocked.up || response.overlapV.x === 0 && response.overlapV.y > 0;
body.blocked.down = body.blocked.down || response.overlapV.x === 0 && response.overlapV.y < 0;
body.blocked.left = body.blocked.left || response.overlapV.y === 0 && response.overlapV.x > 0;
body.blocked.right = body.blocked.right || response.overlapV.y === 0 && response.overlapV.x < 0;
};
/**
* Pull the body into a collision response based on its slopes options.
*
* TODO: Don't return after any condition is met, accumulate values into a
* single SAT.Vector and apply at the end.
*
* @method Phaser.Plugin.ArcadeSlopes.SatSolver#pull
* @param {Phaser.Physics.Arcade.Body} body - The physics body.
* @param {SAT.Response} response - The SAT response.
* @return {boolean} - Whether the body was pulled.
*/
Phaser.Plugin.ArcadeSlopes.SatSolver.prototype.pull = function (body, response) {
if (!body.slopes.pullUp && !body.slopes.pullDown && !body.slopes.pullLeft && !body.slopes.pullRight &&
!body.slopes.pullTopLeft && !body.slopes.pullTopRight && !body.slopes.pullBottomLeft && !body.slopes.pullBottomRight) {
return false;
}
// Clone and flip the overlap normal so that it faces into the collision
var overlapN = response.overlapN.clone().scale(-1);
if (body.slopes.pullUp && overlapN.y < 0) {
// Scale it by the configured amount
pullUp = overlapN.clone().scale(body.slopes.pullUp);
// Apply it to the body velocity
body.velocity.x += pullUp.x;
body.velocity.y += pullUp.y;
return true;
}
if (body.slopes.pullDown && overlapN.y > 0) {
pullDown = overlapN.clone().scale(body.slopes.pullDown);
body.velocity.x += pullDown.x;
body.velocity.y += pullDown.y;
return true;
}
if (body.slopes.pullLeft && overlapN.x < 0) {
pullLeft = overlapN.clone().scale(body.slopes.pullLeft);
body.velocity.x += pullLeft.x;
body.velocity.y += pullLeft.y;
return true;
}
if (body.slopes.pullRight && overlapN.x > 0) {
pullRight = overlapN.clone().scale(body.slopes.pullRight);
body.velocity.x += pullRight.x;
body.velocity.y += pullRight.y;
return true;
}
if (body.slopes.pullTopLeft && overlapN.x < 0 && overlapN.y < 0) {
pullUp = overlapN.clone().scale(body.slopes.pullTopLeft);
body.velocity.x += pullUp.x;
body.velocity.y += pullUp.y;
return true;
}
if (body.slopes.pullTopRight && overlapN.x > 0 && overlapN.y < 0) {
pullDown = overlapN.clone().scale(body.slopes.pullTopRight);
body.velocity.x += pullDown.x;
body.velocity.y += pullDown.y;
return true;
}
if (body.slopes.pullBottomLeft && overlapN.x < 0 && overlapN.y > 0) {
pullLeft = overlapN.clone().scale(body.slopes.pullBottomLeft);
body.velocity.x += pullLeft.x;
body.velocity.y += pullLeft.y;
return true;
}
if (body.slopes.pullBottomRight && overlapN.x > 0 && overlapN.y > 0) {
pullRight = overlapN.clone().scale(body.slopes.pullBottomRight);
body.velocity.x += pullRight.x;
body.velocity.y += pullRight.y;
return true;
}
return false;
};
/**
* Determine whether everything required to process a collision is available.
*
* @method Phaser.Plugin.ArcadeSlopes.SatSolver#shouldCollide
* @param {Phaser.Physics.Arcade.Body} body - The physics body.
* @param {Phaser.Tile} tile - The tile.
* @return {boolean}
*/
Phaser.Plugin.ArcadeSlopes.SatSolver.prototype.shouldCollide = function (body, tile) {
return body.enable && body.polygon && body.slopes && tile.collides && tile.slope && tile.slope.polygon;
};
/**
* Flattens the specified array of points onto a unit vector axis,
* resulting in a one dimensional range of the minimum and
* maximum value on that axis.
*
* Copied verbatim from SAT.flattenPointsOn.
*
* @see SAT.flattenPointsOn
* @static
* @method Phaser.Plugin.ArcadeSlopes.SatSolver#flattenPointsOn
* @param {SAT.Vector[]} points - The points to flatten.
* @param {SAT.Vector} normal - The unit vector axis to flatten on.
* @param {number[]} result - An array. After calling this,
* result[0] will be the minimum value,
* result[1] will be the maximum value.
*/
Phaser.Plugin.ArcadeSlopes.SatSolver.flattenPointsOn = function (points, normal, result) {
var min = Number.MAX_VALUE;
var max = -Number.MAX_VALUE;
var len = points.length;
for (var i = 0; i < len; i++ ) {
// The magnitude of the projection of the point onto the normal
var dot = points[i].dot(normal);
if (dot < min) { min = dot; }
if (dot > max) { max = dot; }
}
result[0] = min; result[1] = max;
};
/**
* Determine whether two polygons are separated by a given axis.
*
* Tailored to only push out in the direction of the given axis.
*
* Adapted from SAT.isSeparatingAxis.
*
* @see {SAT.isSeparatingAxis}
* @method Phaser.Plugin.ArcadeSlopes.SatSolver#isSeparatingAxis
* @param {SAT.Polygon} a - The first polygon.
* @param {SAT.Polygon} b - The second polygon.
* @param {SAT.Vector} axis - The axis (unit sized) to test against.
* The points of both polygons are projected
* onto this axis.
* @param {SAT.Response} response - The response to populate if the polygons are
* not separated by the given axis.
* @return {boolean} true if it is a separating axis, false otherwise. If false,
* and a response is passed in, information about how much overlap and
* the direction of the overlap will be populated.
*/
Phaser.Plugin.ArcadeSlopes.SatSolver.prototype.isSeparatingAxis = function (a, b, axis, response) {
var aPos = a.pos;
var bPos = b.pos;
var aPoints = a.calcPoints;
var bPoints = b.calcPoints;
var rangeA = this.arrayPool.pop();
var rangeB = this.arrayPool.pop();
// The magnitude of the offset between the two polygons
var offsetV = this.vectorPool.pop().copy(bPos).sub(aPos);
var projectedOffset = offsetV.dot(axis);
// Project the polygons onto the axis.
Phaser.Plugin.ArcadeSlopes.SatSolver.flattenPointsOn(aPoints, axis, rangeA);
Phaser.Plugin.ArcadeSlopes.SatSolver.flattenPointsOn(bPoints, axis, rangeB);
// Move B's range to its position relative to A.
rangeB[0] += projectedOffset;
rangeB[1] += projectedOffset;
// Check if there is a gap. If there is, this is a separating axis and we can stop
if (rangeA[0] >= rangeB[1] || rangeB[0] >= rangeA[1]) {
this.vectorPool.push(offsetV);
this.arrayPool.push(rangeA);
this.arrayPool.push(rangeB);
return true;
}
var option1, option2;
// This is not a separating axis. If we're calculating a response, calculate
// the overlap
var overlap = 0;
if (rangeA[0] < rangeB[0]) {
// A starts further left than B
response.aInB = false;
if (rangeA[1] < rangeB[1]) {
// A ends before B does. We have to pull A out of B
//overlap = rangeA[1] - rangeB[0];
response.bInA = false;
}// else {
// B is fully inside A. Pick the shortest way out.
//option1 = rangeA[1] - rangeB[0];
//option2 = rangeB[1] - rangeA[0];
//overlap = option1 < option2 ? option1 : -option2;
//}
} else {
// B starts further left than A
response.bInA = false;
if (rangeA[1] > rangeB[1]) {
// B ends before A ends. We have to push A out of B
overlap = rangeA[0] - rangeB[1];
response.aInB = false;
} else {
// A is fully inside B. Pick the shortest way out.
option1 = rangeA[1] - rangeB[0];
option2 = rangeB[1] - rangeA[0];
//overlap = option1 < option2 ? option1 : -option2;
if (option1 >= option2) {
overlap = -option2;
}
}
}
// If this is the smallest amount of overlap we've seen so far, set it
// as the minimum overlap.
var absOverlap = Math.abs(overlap);
if (absOverlap < response.overlap) {
response.overlap = absOverlap;
response.overlapN.copy(axis);
if (overlap < 0) {
response.overlapN.reverse();
}
}
this.vectorPool.push(offsetV);
this.arrayPool.push(rangeA);
this.arrayPool.push(rangeB);
return false;
};
/**
* Test whether two polygons overlap.
*
* Takes a response object that will be populated with the shortest
* viable separation vector. Ignores collision responses that don't oppose
* velocity enough.
*
* Returns true if there is a collision and false otherwise.
*
* Tailored to work with an AABB as the first polygon.
*
* Adapted from SAT.testPolygonPolygon.
*
* @see {SAT.testPolygonPolygon}
* @method Phaser.Plugin.ArcadeSlopes.SatSolver#testPolygonPolygon
* @param {SAT.Polygon} a - The first polygon.
* @param {SAT.Polygon} b - The second polygon.
* @param {SAT.Response} response - The response object to populate with overlap information.
* @param {SAT.Vector} velocity - The velocity vector to ignore.
* @param {SAT.Vector[]} ignore - The axes to ignore.
* @return {boolean} - Whether the the two polygons overlap.
*/
Phaser.Plugin.ArcadeSlopes.SatSolver.prototype.testPolygonPolygon = function (a, b, response, velocity, ignore) {
var aPoints = a.calcPoints;
var aLen = aPoints.length;
var bPoints = b.calcPoints;
var bLen = bPoints.length;
var i, j, k;
var responses = this.arrayPool.pop();
var axes = this.arrayPool.pop();
responses.length = 0;
axes.length = 0;
// If any of the edge normals of A is a separating axis, no intersection
for (i = 0; i < aLen; i++) {
responses[i] = this.responsePool.pop();
responses[i].clear();
axes[i] = a.normals[i];
if (this.isSeparatingAxis(a, b, a.normals[i], responses[i])) {
for (k = 0; k < responses.length; k++) {
this.responsePool.push(responses[k]);
}
this.arrayPool.push(responses, axes);
return false;
}
}
// If any of the edge normals of B is a separating axis, no intersection
for (i = 0, j = aLen; i < bLen; i++, j++) {
responses[j] = this.responsePool.pop();
responses[j].clear();
axes[j] = b.normals[i];
if (this.isSeparatingAxis(a, b, b.normals[i], responses[j])) {
for (k = 0; k < responses.length; k++) {
this.responsePool.push(responses[k]);
}
this.arrayPool.push(responses, axes);
return false;
}
}
// Since none of the edge normals of A or B are a separating axis, there is
// an intersection
var viable = false;
var ignored = false;
var velocityTestVector = this.vectorPool.pop();
// Determine the shortest desirable and viable separation from the responses
for (i = 0; i < responses.length; i++) {
// Is the overlap in the range we want?
// TODO: Less than the max of tile width/height?
if (!(responses[i].overlap > 0 && responses[i].overlap < Number.MAX_VALUE)) {
continue;
}
// Is the overlap direction too close to that of the velocity direction?
if (velocity && velocityTestVector.copy(responses[i].overlapN).scale(-1).dot(velocity) > 0) {
continue;
}
ignored = false;
// Is the axis of the overlap in the extra ignore list?
for (j = 0; j < ignore.length; j++) {
if (axes[i].x === ignore[j].x && axes[i].y === ignore[j].y) {
ignored = true;
break;
}
}
// Skip this response if its normal is ignored
if (ignored) {
continue;
}
// Is this response's overlap shorter than that of the current?
if (responses[i].overlap < response.overlap) {
viable = true;
response.aInB = responses[i].aInB;
response.bInA = responses[i].bInA;
response.overlap = responses[i].overlap;
response.overlapN = responses[i].overlapN;
}
}
// Set the polygons on the response and calculate the overlap vector
if (viable) {
response.a = a;
response.b = b;
response.overlapV.copy(response.overlapN).scale(response.overlap);
}
// Recycle the temporary responses, arrays and vectors used for calculations
for (k = 0; k < responses.length; k++) {
this.responsePool.push(responses[k]);
}
this.arrayPool.push(responses, axes);
this.vectorPool.push(velocityTestVector);
return viable;
};
/**
* Separate the given body and tile from each other and apply any relevant
* changes to the body's velocity.
*
* @method Phaser.Plugin.ArcadeSlopes.SatSolver#collide
* @param {integer} i - The tile index.
* @param {Phaser.Physics.Arcade.Body} body - The physics body.
* @param {Phaser.Tile} tile - The tile.
* @param {Phaser.TilemapLayer} tilemapLayer - The tilemap layer.
* @param {boolean} overlapOnly - Whether to only check for an overlap.
* @return {boolean} - Whether the body was separated.
*/
Phaser.Plugin.ArcadeSlopes.SatSolver.prototype.collide = function (i, body, tile, tilemapLayer, overlapOnly) {
// Update the body's polygon position and velocity vector
this.updateValues(body);
// Bail out if we don't have everything we need
if (!this.shouldCollide(body, tile)) {