UNPKG

phaser

Version:

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

462 lines (382 loc) 14.5 kB
/** * The `Matter.Vertices` module contains methods for creating and manipulating sets of vertices. * A set of vertices is an array of `Matter.Vector` with additional indexing properties inserted by `Vertices.create`. * A `Matter.Body` maintains a set of vertices to represent the shape of the object (its convex hull). * * See the included usage [examples](https://github.com/liabru/matter-js/tree/master/examples). * * @class Vertices */ var Vertices = {}; module.exports = Vertices; var Vector = require('../geometry/Vector'); var Common = require('../core/Common'); (function() { /** * Creates a new set of `Matter.Body` compatible vertices. * The `points` argument accepts an array of `Matter.Vector` points orientated around the origin `(0, 0)`, for example: * * [{ x: 0, y: 0 }, { x: 25, y: 50 }, { x: 50, y: 0 }] * * The `Vertices.create` method returns a new array of vertices, which are similar to Matter.Vector objects, * but with some additional references required for efficient collision detection routines. * * Vertices must be specified in clockwise order. * * Note that the `body` argument is not optional, a `Matter.Body` reference must be provided. * * @method create * @param {vector[]} points * @param {body} body */ Vertices.create = function(points, body) { var vertices = []; for (var i = 0; i < points.length; i++) { var point = points[i], vertex = { x: point.x, y: point.y, index: i, body: body, isInternal: false }; vertices.push(vertex); } return vertices; }; /** * Parses a string containing ordered x y pairs separated by spaces (and optionally commas), * into a `Matter.Vertices` object for the given `Matter.Body`. * For parsing SVG paths, see `Svg.pathToVertices`. * @method fromPath * @param {string} path * @param {body} body * @return {vertices} vertices */ Vertices.fromPath = function(path, body) { var pathPattern = /L?\s*([-\d.e]+)[\s,]*([-\d.e]+)*/ig, points = []; path.replace(pathPattern, function(match, x, y) { points.push({ x: parseFloat(x), y: parseFloat(y) }); }); return Vertices.create(points, body); }; /** * Returns the centre (centroid) of the set of vertices. * @method centre * @param {vertices} vertices * @return {vector} The centre point */ Vertices.centre = function(vertices) { var area = Vertices.area(vertices, true), centre = { x: 0, y: 0 }, cross, temp, j; for (var i = 0; i < vertices.length; i++) { j = (i + 1) % vertices.length; cross = Vector.cross(vertices[i], vertices[j]); temp = Vector.mult(Vector.add(vertices[i], vertices[j]), cross); centre = Vector.add(centre, temp); } return Vector.div(centre, 6 * area); }; /** * Returns the average (mean) of the set of vertices. * @method mean * @param {vertices} vertices * @return {vector} The average point */ Vertices.mean = function(vertices) { var average = { x: 0, y: 0 }; for (var i = 0; i < vertices.length; i++) { average.x += vertices[i].x; average.y += vertices[i].y; } return Vector.div(average, vertices.length); }; /** * Returns the area of the set of vertices. * @method area * @param {vertices} vertices * @param {bool} signed * @return {number} The area */ Vertices.area = function(vertices, signed) { var area = 0, j = vertices.length - 1; for (var i = 0; i < vertices.length; i++) { area += (vertices[j].x - vertices[i].x) * (vertices[j].y + vertices[i].y); j = i; } if (signed) return area / 2; return Math.abs(area) / 2; }; /** * Returns the moment of inertia (second moment of area) of the set of vertices given the total mass. * @method inertia * @param {vertices} vertices * @param {number} mass * @return {number} The polygon's moment of inertia */ Vertices.inertia = function(vertices, mass) { var numerator = 0, denominator = 0, v = vertices, cross, j; // find the polygon's moment of inertia, using second moment of area // from equations at http://www.physicsforums.com/showthread.php?t=25293 for (var n = 0; n < v.length; n++) { j = (n + 1) % v.length; cross = Math.abs(Vector.cross(v[j], v[n])); numerator += cross * (Vector.dot(v[j], v[j]) + Vector.dot(v[j], v[n]) + Vector.dot(v[n], v[n])); denominator += cross; } return (mass / 6) * (numerator / denominator); }; /** * Translates the set of vertices in-place. * @method translate * @param {vertices} vertices * @param {vector} vector * @param {number} scalar */ Vertices.translate = function(vertices, vector, scalar) { scalar = typeof scalar !== 'undefined' ? scalar : 1; var verticesLength = vertices.length, translateX = vector.x * scalar, translateY = vector.y * scalar, i; for (i = 0; i < verticesLength; i++) { vertices[i].x += translateX; vertices[i].y += translateY; } return vertices; }; /** * Rotates the set of vertices in-place. * @method rotate * @param {vertices} vertices * @param {number} angle * @param {vector} point */ Vertices.rotate = function(vertices, angle, point) { if (angle === 0) return; var cos = Math.cos(angle), sin = Math.sin(angle), pointX = point.x, pointY = point.y, verticesLength = vertices.length, vertex, dx, dy, i; for (i = 0; i < verticesLength; i++) { vertex = vertices[i]; dx = vertex.x - pointX; dy = vertex.y - pointY; vertex.x = pointX + (dx * cos - dy * sin); vertex.y = pointY + (dx * sin + dy * cos); } return vertices; }; /** * Returns `true` if the `point` is inside the set of `vertices`. * @method contains * @param {vertices} vertices * @param {vector} point * @return {boolean} True if the vertices contains point, otherwise false */ Vertices.contains = function(vertices, point) { var pointX = point.x, pointY = point.y, verticesLength = vertices.length, vertex = vertices[verticesLength - 1], nextVertex; for (var i = 0; i < verticesLength; i++) { nextVertex = vertices[i]; if ((pointX - vertex.x) * (nextVertex.y - vertex.y) + (pointY - vertex.y) * (vertex.x - nextVertex.x) > 0) { return false; } vertex = nextVertex; } return true; }; /** * Scales the vertices from a point (default is centre) in-place. * @method scale * @param {vertices} vertices * @param {number} scaleX * @param {number} scaleY * @param {vector} point */ Vertices.scale = function(vertices, scaleX, scaleY, point) { if (scaleX === 1 && scaleY === 1) return vertices; point = point || Vertices.centre(vertices); var vertex, delta; for (var i = 0; i < vertices.length; i++) { vertex = vertices[i]; delta = Vector.sub(vertex, point); vertices[i].x = point.x + delta.x * scaleX; vertices[i].y = point.y + delta.y * scaleY; } return vertices; }; /** * Chamfers a set of vertices by giving them rounded corners, returns a new set of vertices. * The radius parameter is a single number or an array to specify the radius for each vertex. * @method chamfer * @param {vertices} vertices * @param {number[]} radius * @param {number} quality * @param {number} qualityMin * @param {number} qualityMax */ Vertices.chamfer = function(vertices, radius, quality, qualityMin, qualityMax) { if (typeof radius === 'number') { radius = [radius]; } else { radius = radius || [8]; } // quality defaults to -1, which is auto quality = (typeof quality !== 'undefined') ? quality : -1; qualityMin = qualityMin || 2; qualityMax = qualityMax || 14; var newVertices = []; for (var i = 0; i < vertices.length; i++) { var prevVertex = vertices[i - 1 >= 0 ? i - 1 : vertices.length - 1], vertex = vertices[i], nextVertex = vertices[(i + 1) % vertices.length], currentRadius = radius[i < radius.length ? i : radius.length - 1]; if (currentRadius === 0) { newVertices.push(vertex); continue; } var prevNormal = Vector.normalise({ x: vertex.y - prevVertex.y, y: prevVertex.x - vertex.x }); var nextNormal = Vector.normalise({ x: nextVertex.y - vertex.y, y: vertex.x - nextVertex.x }); var diagonalRadius = Math.sqrt(2 * Math.pow(currentRadius, 2)), radiusVector = Vector.mult(Common.clone(prevNormal), currentRadius), midNormal = Vector.normalise(Vector.mult(Vector.add(prevNormal, nextNormal), 0.5)), scaledVertex = Vector.sub(vertex, Vector.mult(midNormal, diagonalRadius)); var precision = quality; if (quality === -1) { // automatically decide precision precision = Math.pow(currentRadius, 0.32) * 1.75; } precision = Common.clamp(precision, qualityMin, qualityMax); // use an even value for precision, more likely to reduce axes by using symmetry if (precision % 2 === 1) precision += 1; var alpha = Math.acos(Vector.dot(prevNormal, nextNormal)), theta = alpha / precision; for (var j = 0; j < precision; j++) { newVertices.push(Vector.add(Vector.rotate(radiusVector, theta * j), scaledVertex)); } } return newVertices; }; /** * Sorts the input vertices into clockwise order in place. * @method clockwiseSort * @param {vertices} vertices * @return {vertices} vertices */ Vertices.clockwiseSort = function(vertices) { var centre = Vertices.mean(vertices); vertices.sort(function(vertexA, vertexB) { return Vector.angle(centre, vertexA) - Vector.angle(centre, vertexB); }); return vertices; }; /** * Returns true if the vertices form a convex shape (vertices must be in clockwise order). * @method isConvex * @param {vertices} vertices * @return {bool} `true` if the `vertices` are convex, `false` if not (or `null` if not computable). */ Vertices.isConvex = function(vertices) { // http://paulbourke.net/geometry/polygonmesh/ // Copyright (c) Paul Bourke (use permitted) var flag = 0, n = vertices.length, i, j, k, z; if (n < 3) return null; for (i = 0; i < n; i++) { j = (i + 1) % n; k = (i + 2) % n; z = (vertices[j].x - vertices[i].x) * (vertices[k].y - vertices[j].y); z -= (vertices[j].y - vertices[i].y) * (vertices[k].x - vertices[j].x); if (z < 0) { flag |= 1; } else if (z > 0) { flag |= 2; } if (flag === 3) { return false; } } if (flag !== 0){ return true; } else { return null; } }; /** * Returns the convex hull of the input vertices as a new array of points. * @method hull * @param {vertices} vertices * @return [vertex] vertices */ Vertices.hull = function(vertices) { // http://geomalgorithms.com/a10-_hull-1.html var upper = [], lower = [], vertex, i; // sort vertices on x-axis (y-axis for ties) vertices = vertices.slice(0); vertices.sort(function(vertexA, vertexB) { var dx = vertexA.x - vertexB.x; return dx !== 0 ? dx : vertexA.y - vertexB.y; }); // build lower hull for (i = 0; i < vertices.length; i += 1) { vertex = vertices[i]; while (lower.length >= 2 && Vector.cross3(lower[lower.length - 2], lower[lower.length - 1], vertex) <= 0) { lower.pop(); } lower.push(vertex); } // build upper hull for (i = vertices.length - 1; i >= 0; i -= 1) { vertex = vertices[i]; while (upper.length >= 2 && Vector.cross3(upper[upper.length - 2], upper[upper.length - 1], vertex) <= 0) { upper.pop(); } upper.push(vertex); } // concatenation of the lower and upper hulls gives the convex hull // omit last points because they are repeated at the beginning of the other list upper.pop(); lower.pop(); return upper.concat(lower); }; })();