mirror of
https://github.com/liabru/matter-js.git
synced 2024-12-26 13:49:01 -05:00
added Demo.concave, added Bodies.fromVertices, added Vertices.isConvex, fixed compound properties
This commit is contained in:
parent
d2f79b4689
commit
0b8efa5c40
6 changed files with 842 additions and 8 deletions
|
@ -6,6 +6,9 @@
|
||||||
<meta name="viewport" content="width=device-width,minimal-ui">
|
<meta name="viewport" content="width=device-width,minimal-ui">
|
||||||
<meta name="robots" content="noindex">
|
<meta name="robots" content="noindex">
|
||||||
|
|
||||||
|
<!-- only required if using Bodies.fromVertices with concave vertices -->
|
||||||
|
<script type="text/javascript" src="./js/lib/decomp.js"></script>
|
||||||
|
|
||||||
<!-- only required if using Matter.RenderPixi -->
|
<!-- only required if using Matter.RenderPixi -->
|
||||||
<script type="text/javascript" src="./js/lib/pixi.dev.js"></script>
|
<script type="text/javascript" src="./js/lib/pixi.dev.js"></script>
|
||||||
|
|
||||||
|
@ -32,6 +35,7 @@
|
||||||
<option value="mixed">Mixed Shapes</option>
|
<option value="mixed">Mixed Shapes</option>
|
||||||
<option value="mixedSolid">Solid Rendering</option>
|
<option value="mixedSolid">Solid Rendering</option>
|
||||||
<option value="compound">Compound Bodies</option>
|
<option value="compound">Compound Bodies</option>
|
||||||
|
<option value="concave">Concave Bodies</option>
|
||||||
<option value="newtonsCradle">Newton's Cradle</option>
|
<option value="newtonsCradle">Newton's Cradle</option>
|
||||||
<option value="wreckingBall">Wrecking Ball</option>
|
<option value="wreckingBall">Wrecking Ball</option>
|
||||||
<option value="slingshot">Slingshot Game</option>
|
<option value="slingshot">Slingshot Game</option>
|
||||||
|
|
|
@ -131,7 +131,24 @@
|
||||||
|
|
||||||
World.add(_world, compound);
|
World.add(_world, compound);
|
||||||
|
|
||||||
_world.gravity.y = 1;
|
var renderOptions = _engine.render.options;
|
||||||
|
renderOptions.showCollisions = true;
|
||||||
|
renderOptions.showBounds = true;
|
||||||
|
renderOptions.showAxes = true;
|
||||||
|
renderOptions.showPositions = true;
|
||||||
|
renderOptions.showConvexHulls = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
Demo.concave = function() {
|
||||||
|
var _world = _engine.world;
|
||||||
|
|
||||||
|
Demo.reset();
|
||||||
|
|
||||||
|
var vertices = Matter.Vertices.fromPath('354 89,336 118,310 145,302 227,322 271,375 292,490 289,539 271,540 233,549 133,526 100,552 36,601 63,633 122,628 227,594 304,505 340,426 340,327 330,265 294,246 242,246 181,256 133,283 81,346 44');
|
||||||
|
//var vertices = Matter.Vertices.fromPath('164 171,232 233,213 302,273 241,342 305,316 231,364 170,309 188,281 117,240 182');
|
||||||
|
|
||||||
|
var concave = Bodies.fromVertices(200, 200, vertices);
|
||||||
|
World.add(_world, concave);
|
||||||
|
|
||||||
var renderOptions = _engine.render.options;
|
var renderOptions = _engine.render.options;
|
||||||
renderOptions.showCollisions = true;
|
renderOptions.showCollisions = true;
|
||||||
|
@ -139,6 +156,7 @@
|
||||||
renderOptions.showAxes = true;
|
renderOptions.showAxes = true;
|
||||||
renderOptions.showPositions = true;
|
renderOptions.showPositions = true;
|
||||||
renderOptions.showConvexHulls = true;
|
renderOptions.showConvexHulls = true;
|
||||||
|
//renderOptions.showVertexNumbers = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
Demo.slingshot = function() {
|
Demo.slingshot = function() {
|
||||||
|
|
670
demo/js/lib/decomp.js
Normal file
670
demo/js/lib/decomp.js
Normal file
|
@ -0,0 +1,670 @@
|
||||||
|
!function(e){"object"==typeof exports?module.exports=e():"function"==typeof define&&define.amd?define(e):"undefined"!=typeof window?window.decomp=e():"undefined"!=typeof global?global.decomp=e():"undefined"!=typeof self&&(self.decomp=e())}(function(){var define,module,exports;
|
||||||
|
return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
|
||||||
|
var Scalar = require('./Scalar');
|
||||||
|
|
||||||
|
module.exports = Line;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Container for line-related functions
|
||||||
|
* @class Line
|
||||||
|
*/
|
||||||
|
function Line(){};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute the intersection between two lines.
|
||||||
|
* @static
|
||||||
|
* @method lineInt
|
||||||
|
* @param {Array} l1 Line vector 1
|
||||||
|
* @param {Array} l2 Line vector 2
|
||||||
|
* @param {Number} precision Precision to use when checking if the lines are parallel
|
||||||
|
* @return {Array} The intersection point.
|
||||||
|
*/
|
||||||
|
Line.lineInt = function(l1,l2,precision){
|
||||||
|
precision = precision || 0;
|
||||||
|
var i = [0,0]; // point
|
||||||
|
var a1, b1, c1, a2, b2, c2, det; // scalars
|
||||||
|
a1 = l1[1][1] - l1[0][1];
|
||||||
|
b1 = l1[0][0] - l1[1][0];
|
||||||
|
c1 = a1 * l1[0][0] + b1 * l1[0][1];
|
||||||
|
a2 = l2[1][1] - l2[0][1];
|
||||||
|
b2 = l2[0][0] - l2[1][0];
|
||||||
|
c2 = a2 * l2[0][0] + b2 * l2[0][1];
|
||||||
|
det = a1 * b2 - a2*b1;
|
||||||
|
if (!Scalar.eq(det, 0, precision)) { // lines are not parallel
|
||||||
|
i[0] = (b2 * c1 - b1 * c2) / det;
|
||||||
|
i[1] = (a1 * c2 - a2 * c1) / det;
|
||||||
|
}
|
||||||
|
return i;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if two line segments intersects.
|
||||||
|
* @method segmentsIntersect
|
||||||
|
* @param {Array} p1 The start vertex of the first line segment.
|
||||||
|
* @param {Array} p2 The end vertex of the first line segment.
|
||||||
|
* @param {Array} q1 The start vertex of the second line segment.
|
||||||
|
* @param {Array} q2 The end vertex of the second line segment.
|
||||||
|
* @return {Boolean} True if the two line segments intersect
|
||||||
|
*/
|
||||||
|
Line.segmentsIntersect = function(p1, p2, q1, q2){
|
||||||
|
var dx = p2[0] - p1[0];
|
||||||
|
var dy = p2[1] - p1[1];
|
||||||
|
var da = q2[0] - q1[0];
|
||||||
|
var db = q2[1] - q1[1];
|
||||||
|
|
||||||
|
// segments are parallel
|
||||||
|
if(da*dy - db*dx == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var s = (dx * (q1[1] - p1[1]) + dy * (p1[0] - q1[0])) / (da * dy - db * dx)
|
||||||
|
var t = (da * (p1[1] - q1[1]) + db * (q1[0] - p1[0])) / (db * dx - da * dy)
|
||||||
|
|
||||||
|
return (s>=0 && s<=1 && t>=0 && t<=1);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
},{"./Scalar":4}],2:[function(require,module,exports){
|
||||||
|
module.exports = Point;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Point related functions
|
||||||
|
* @class Point
|
||||||
|
*/
|
||||||
|
function Point(){};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the area of a triangle spanned by the three given points. Note that the area will be negative if the points are not given in counter-clockwise order.
|
||||||
|
* @static
|
||||||
|
* @method area
|
||||||
|
* @param {Array} a
|
||||||
|
* @param {Array} b
|
||||||
|
* @param {Array} c
|
||||||
|
* @return {Number}
|
||||||
|
*/
|
||||||
|
Point.area = function(a,b,c){
|
||||||
|
return (((b[0] - a[0])*(c[1] - a[1]))-((c[0] - a[0])*(b[1] - a[1])));
|
||||||
|
};
|
||||||
|
|
||||||
|
Point.left = function(a,b,c){
|
||||||
|
return Point.area(a,b,c) > 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
Point.leftOn = function(a,b,c) {
|
||||||
|
return Point.area(a, b, c) >= 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
Point.right = function(a,b,c) {
|
||||||
|
return Point.area(a, b, c) < 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
Point.rightOn = function(a,b,c) {
|
||||||
|
return Point.area(a, b, c) <= 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
var tmpPoint1 = [],
|
||||||
|
tmpPoint2 = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if three points are collinear
|
||||||
|
* @method collinear
|
||||||
|
* @param {Array} a
|
||||||
|
* @param {Array} b
|
||||||
|
* @param {Array} c
|
||||||
|
* @param {Number} [thresholdAngle=0] Threshold angle to use when comparing the vectors. The function will return true if the angle between the resulting vectors is less than this value. Use zero for max precision.
|
||||||
|
* @return {Boolean}
|
||||||
|
*/
|
||||||
|
Point.collinear = function(a,b,c,thresholdAngle) {
|
||||||
|
if(!thresholdAngle)
|
||||||
|
return Point.area(a, b, c) == 0;
|
||||||
|
else {
|
||||||
|
var ab = tmpPoint1,
|
||||||
|
bc = tmpPoint2;
|
||||||
|
|
||||||
|
ab[0] = b[0]-a[0];
|
||||||
|
ab[1] = b[1]-a[1];
|
||||||
|
bc[0] = c[0]-b[0];
|
||||||
|
bc[1] = c[1]-b[1];
|
||||||
|
|
||||||
|
var dot = ab[0]*bc[0] + ab[1]*bc[1],
|
||||||
|
magA = Math.sqrt(ab[0]*ab[0] + ab[1]*ab[1]),
|
||||||
|
magB = Math.sqrt(bc[0]*bc[0] + bc[1]*bc[1]),
|
||||||
|
angle = Math.acos(dot/(magA*magB));
|
||||||
|
return angle < thresholdAngle;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Point.sqdist = function(a,b){
|
||||||
|
var dx = b[0] - a[0];
|
||||||
|
var dy = b[1] - a[1];
|
||||||
|
return dx * dx + dy * dy;
|
||||||
|
};
|
||||||
|
|
||||||
|
},{}],3:[function(require,module,exports){
|
||||||
|
var Line = require("./Line")
|
||||||
|
, Point = require("./Point")
|
||||||
|
, Scalar = require("./Scalar")
|
||||||
|
|
||||||
|
module.exports = Polygon;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Polygon class.
|
||||||
|
* @class Polygon
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
function Polygon(){
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vertices that this polygon consists of. An array of array of numbers, example: [[0,0],[1,0],..]
|
||||||
|
* @property vertices
|
||||||
|
* @type {Array}
|
||||||
|
*/
|
||||||
|
this.vertices = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a vertex at position i. It does not matter if i is out of bounds, this function will just cycle.
|
||||||
|
* @method at
|
||||||
|
* @param {Number} i
|
||||||
|
* @return {Array}
|
||||||
|
*/
|
||||||
|
Polygon.prototype.at = function(i){
|
||||||
|
var v = this.vertices,
|
||||||
|
s = v.length;
|
||||||
|
return v[i < 0 ? i % s + s : i % s];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get first vertex
|
||||||
|
* @method first
|
||||||
|
* @return {Array}
|
||||||
|
*/
|
||||||
|
Polygon.prototype.first = function(){
|
||||||
|
return this.vertices[0];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get last vertex
|
||||||
|
* @method last
|
||||||
|
* @return {Array}
|
||||||
|
*/
|
||||||
|
Polygon.prototype.last = function(){
|
||||||
|
return this.vertices[this.vertices.length-1];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear the polygon data
|
||||||
|
* @method clear
|
||||||
|
* @return {Array}
|
||||||
|
*/
|
||||||
|
Polygon.prototype.clear = function(){
|
||||||
|
this.vertices.length = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Append points "from" to "to"-1 from an other polygon "poly" onto this one.
|
||||||
|
* @method append
|
||||||
|
* @param {Polygon} poly The polygon to get points from.
|
||||||
|
* @param {Number} from The vertex index in "poly".
|
||||||
|
* @param {Number} to The end vertex index in "poly". Note that this vertex is NOT included when appending.
|
||||||
|
* @return {Array}
|
||||||
|
*/
|
||||||
|
Polygon.prototype.append = function(poly,from,to){
|
||||||
|
if(typeof(from) == "undefined") throw new Error("From is not given!");
|
||||||
|
if(typeof(to) == "undefined") throw new Error("To is not given!");
|
||||||
|
|
||||||
|
if(to-1 < from) throw new Error("lol1");
|
||||||
|
if(to > poly.vertices.length) throw new Error("lol2");
|
||||||
|
if(from < 0) throw new Error("lol3");
|
||||||
|
|
||||||
|
for(var i=from; i<to; i++){
|
||||||
|
this.vertices.push(poly.vertices[i]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make sure that the polygon vertices are ordered counter-clockwise.
|
||||||
|
* @method makeCCW
|
||||||
|
*/
|
||||||
|
Polygon.prototype.makeCCW = function(){
|
||||||
|
var br = 0,
|
||||||
|
v = this.vertices;
|
||||||
|
|
||||||
|
// find bottom right point
|
||||||
|
for (var i = 1; i < this.vertices.length; ++i) {
|
||||||
|
if (v[i][1] < v[br][1] || (v[i][1] == v[br][1] && v[i][0] > v[br][0])) {
|
||||||
|
br = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// reverse poly if clockwise
|
||||||
|
if (!Point.left(this.at(br - 1), this.at(br), this.at(br + 1))) {
|
||||||
|
this.reverse();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the vertices in the polygon
|
||||||
|
* @method reverse
|
||||||
|
*/
|
||||||
|
Polygon.prototype.reverse = function(){
|
||||||
|
var tmp = [];
|
||||||
|
for(var i=0, N=this.vertices.length; i!==N; i++){
|
||||||
|
tmp.push(this.vertices.pop());
|
||||||
|
}
|
||||||
|
this.vertices = tmp;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a point in the polygon is a reflex point
|
||||||
|
* @method isReflex
|
||||||
|
* @param {Number} i
|
||||||
|
* @return {Boolean}
|
||||||
|
*/
|
||||||
|
Polygon.prototype.isReflex = function(i){
|
||||||
|
return Point.right(this.at(i - 1), this.at(i), this.at(i + 1));
|
||||||
|
};
|
||||||
|
|
||||||
|
var tmpLine1=[],
|
||||||
|
tmpLine2=[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if two vertices in the polygon can see each other
|
||||||
|
* @method canSee
|
||||||
|
* @param {Number} a Vertex index 1
|
||||||
|
* @param {Number} b Vertex index 2
|
||||||
|
* @return {Boolean}
|
||||||
|
*/
|
||||||
|
Polygon.prototype.canSee = function(a,b) {
|
||||||
|
var p, dist, l1=tmpLine1, l2=tmpLine2;
|
||||||
|
|
||||||
|
if (Point.leftOn(this.at(a + 1), this.at(a), this.at(b)) && Point.rightOn(this.at(a - 1), this.at(a), this.at(b))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
dist = Point.sqdist(this.at(a), this.at(b));
|
||||||
|
for (var i = 0; i !== this.vertices.length; ++i) { // for each edge
|
||||||
|
if ((i + 1) % this.vertices.length === a || i === a) // ignore incident edges
|
||||||
|
continue;
|
||||||
|
if (Point.leftOn(this.at(a), this.at(b), this.at(i + 1)) && Point.rightOn(this.at(a), this.at(b), this.at(i))) { // if diag intersects an edge
|
||||||
|
l1[0] = this.at(a);
|
||||||
|
l1[1] = this.at(b);
|
||||||
|
l2[0] = this.at(i);
|
||||||
|
l2[1] = this.at(i + 1);
|
||||||
|
p = Line.lineInt(l1,l2);
|
||||||
|
if (Point.sqdist(this.at(a), p) < dist) { // if edge is blocking visibility to b
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy the polygon from vertex i to vertex j.
|
||||||
|
* @method copy
|
||||||
|
* @param {Number} i
|
||||||
|
* @param {Number} j
|
||||||
|
* @param {Polygon} [targetPoly] Optional target polygon to save in.
|
||||||
|
* @return {Polygon} The resulting copy.
|
||||||
|
*/
|
||||||
|
Polygon.prototype.copy = function(i,j,targetPoly){
|
||||||
|
var p = targetPoly || new Polygon();
|
||||||
|
p.clear();
|
||||||
|
if (i < j) {
|
||||||
|
// Insert all vertices from i to j
|
||||||
|
for(var k=i; k<=j; k++)
|
||||||
|
p.vertices.push(this.vertices[k]);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// Insert vertices 0 to j
|
||||||
|
for(var k=0; k<=j; k++)
|
||||||
|
p.vertices.push(this.vertices[k]);
|
||||||
|
|
||||||
|
// Insert vertices i to end
|
||||||
|
for(var k=i; k<this.vertices.length; k++)
|
||||||
|
p.vertices.push(this.vertices[k]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return p;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decomposes the polygon into convex pieces. Returns a list of edges [[p1,p2],[p2,p3],...] that cuts the polygon.
|
||||||
|
* Note that this algorithm has complexity O(N^4) and will be very slow for polygons with many vertices.
|
||||||
|
* @method getCutEdges
|
||||||
|
* @return {Array}
|
||||||
|
*/
|
||||||
|
Polygon.prototype.getCutEdges = function() {
|
||||||
|
var min=[], tmp1=[], tmp2=[], tmpPoly = new Polygon();
|
||||||
|
var nDiags = Number.MAX_VALUE;
|
||||||
|
|
||||||
|
for (var i = 0; i < this.vertices.length; ++i) {
|
||||||
|
if (this.isReflex(i)) {
|
||||||
|
for (var j = 0; j < this.vertices.length; ++j) {
|
||||||
|
if (this.canSee(i, j)) {
|
||||||
|
tmp1 = this.copy(i, j, tmpPoly).getCutEdges();
|
||||||
|
tmp2 = this.copy(j, i, tmpPoly).getCutEdges();
|
||||||
|
|
||||||
|
for(var k=0; k<tmp2.length; k++)
|
||||||
|
tmp1.push(tmp2[k]);
|
||||||
|
|
||||||
|
if (tmp1.length < nDiags) {
|
||||||
|
min = tmp1;
|
||||||
|
nDiags = tmp1.length;
|
||||||
|
min.push([this.at(i), this.at(j)]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return min;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decomposes the polygon into one or more convex sub-Polygons.
|
||||||
|
* @method decomp
|
||||||
|
* @return {Array} An array or Polygon objects.
|
||||||
|
*/
|
||||||
|
Polygon.prototype.decomp = function(){
|
||||||
|
var edges = this.getCutEdges();
|
||||||
|
if(edges.length > 0)
|
||||||
|
return this.slice(edges);
|
||||||
|
else
|
||||||
|
return [this];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Slices the polygon given one or more cut edges. If given one, this function will return two polygons (false on failure). If many, an array of polygons.
|
||||||
|
* @method slice
|
||||||
|
* @param {Array} cutEdges A list of edges, as returned by .getCutEdges()
|
||||||
|
* @return {Array}
|
||||||
|
*/
|
||||||
|
Polygon.prototype.slice = function(cutEdges){
|
||||||
|
if(cutEdges.length == 0) return [this];
|
||||||
|
if(cutEdges instanceof Array && cutEdges.length && cutEdges[0] instanceof Array && cutEdges[0].length==2 && cutEdges[0][0] instanceof Array){
|
||||||
|
|
||||||
|
var polys = [this];
|
||||||
|
|
||||||
|
for(var i=0; i<cutEdges.length; i++){
|
||||||
|
var cutEdge = cutEdges[i];
|
||||||
|
// Cut all polys
|
||||||
|
for(var j=0; j<polys.length; j++){
|
||||||
|
var poly = polys[j];
|
||||||
|
var result = poly.slice(cutEdge);
|
||||||
|
if(result){
|
||||||
|
// Found poly! Cut and quit
|
||||||
|
polys.splice(j,1);
|
||||||
|
polys.push(result[0],result[1]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return polys;
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// Was given one edge
|
||||||
|
var cutEdge = cutEdges;
|
||||||
|
var i = this.vertices.indexOf(cutEdge[0]);
|
||||||
|
var j = this.vertices.indexOf(cutEdge[1]);
|
||||||
|
|
||||||
|
if(i != -1 && j != -1){
|
||||||
|
return [this.copy(i,j),
|
||||||
|
this.copy(j,i)];
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks that the line segments of this polygon do not intersect each other.
|
||||||
|
* @method isSimple
|
||||||
|
* @param {Array} path An array of vertices e.g. [[0,0],[0,1],...]
|
||||||
|
* @return {Boolean}
|
||||||
|
* @todo Should it check all segments with all others?
|
||||||
|
*/
|
||||||
|
Polygon.prototype.isSimple = function(){
|
||||||
|
var path = this.vertices;
|
||||||
|
// Check
|
||||||
|
for(var i=0; i<path.length-1; i++){
|
||||||
|
for(var j=0; j<i-1; j++){
|
||||||
|
if(Line.segmentsIntersect(path[i], path[i+1], path[j], path[j+1] )){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the segment between the last and the first point to all others
|
||||||
|
for(var i=1; i<path.length-2; i++){
|
||||||
|
if(Line.segmentsIntersect(path[0], path[path.length-1], path[i], path[i+1] )){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
function getIntersectionPoint(p1, p2, q1, q2, delta){
|
||||||
|
delta = delta || 0;
|
||||||
|
var a1 = p2[1] - p1[1];
|
||||||
|
var b1 = p1[0] - p2[0];
|
||||||
|
var c1 = (a1 * p1[0]) + (b1 * p1[1]);
|
||||||
|
var a2 = q2[1] - q1[1];
|
||||||
|
var b2 = q1[0] - q2[0];
|
||||||
|
var c2 = (a2 * q1[0]) + (b2 * q1[1]);
|
||||||
|
var det = (a1 * b2) - (a2 * b1);
|
||||||
|
|
||||||
|
if(!Scalar.eq(det,0,delta))
|
||||||
|
return [((b2 * c1) - (b1 * c2)) / det, ((a1 * c2) - (a2 * c1)) / det]
|
||||||
|
else
|
||||||
|
return [0,0]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Quickly decompose the Polygon into convex sub-polygons.
|
||||||
|
* @method quickDecomp
|
||||||
|
* @param {Array} result
|
||||||
|
* @param {Array} [reflexVertices]
|
||||||
|
* @param {Array} [steinerPoints]
|
||||||
|
* @param {Number} [delta]
|
||||||
|
* @param {Number} [maxlevel]
|
||||||
|
* @param {Number} [level]
|
||||||
|
* @return {Array}
|
||||||
|
*/
|
||||||
|
Polygon.prototype.quickDecomp = function(result,reflexVertices,steinerPoints,delta,maxlevel,level){
|
||||||
|
maxlevel = maxlevel || 100;
|
||||||
|
level = level || 0;
|
||||||
|
delta = delta || 25;
|
||||||
|
result = typeof(result)!="undefined" ? result : [];
|
||||||
|
reflexVertices = reflexVertices || [];
|
||||||
|
steinerPoints = steinerPoints || [];
|
||||||
|
|
||||||
|
var upperInt=[0,0], lowerInt=[0,0], p=[0,0]; // Points
|
||||||
|
var upperDist=0, lowerDist=0, d=0, closestDist=0; // scalars
|
||||||
|
var upperIndex=0, lowerIndex=0, closestIndex=0; // Integers
|
||||||
|
var lowerPoly=new Polygon(), upperPoly=new Polygon(); // polygons
|
||||||
|
var poly = this,
|
||||||
|
v = this.vertices;
|
||||||
|
|
||||||
|
if(v.length < 3) return result;
|
||||||
|
|
||||||
|
level++;
|
||||||
|
if(level > maxlevel){
|
||||||
|
console.warn("quickDecomp: max level ("+maxlevel+") reached.");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < this.vertices.length; ++i) {
|
||||||
|
if (poly.isReflex(i)) {
|
||||||
|
reflexVertices.push(poly.vertices[i]);
|
||||||
|
upperDist = lowerDist = Number.MAX_VALUE;
|
||||||
|
|
||||||
|
|
||||||
|
for (var j = 0; j < this.vertices.length; ++j) {
|
||||||
|
if (Point.left(poly.at(i - 1), poly.at(i), poly.at(j))
|
||||||
|
&& Point.rightOn(poly.at(i - 1), poly.at(i), poly.at(j - 1))) { // if line intersects with an edge
|
||||||
|
p = getIntersectionPoint(poly.at(i - 1), poly.at(i), poly.at(j), poly.at(j - 1)); // find the point of intersection
|
||||||
|
if (Point.right(poly.at(i + 1), poly.at(i), p)) { // make sure it's inside the poly
|
||||||
|
d = Point.sqdist(poly.vertices[i], p);
|
||||||
|
if (d < lowerDist) { // keep only the closest intersection
|
||||||
|
lowerDist = d;
|
||||||
|
lowerInt = p;
|
||||||
|
lowerIndex = j;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Point.left(poly.at(i + 1), poly.at(i), poly.at(j + 1))
|
||||||
|
&& Point.rightOn(poly.at(i + 1), poly.at(i), poly.at(j))) {
|
||||||
|
p = getIntersectionPoint(poly.at(i + 1), poly.at(i), poly.at(j), poly.at(j + 1));
|
||||||
|
if (Point.left(poly.at(i - 1), poly.at(i), p)) {
|
||||||
|
d = Point.sqdist(poly.vertices[i], p);
|
||||||
|
if (d < upperDist) {
|
||||||
|
upperDist = d;
|
||||||
|
upperInt = p;
|
||||||
|
upperIndex = j;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if there are no vertices to connect to, choose a point in the middle
|
||||||
|
if (lowerIndex == (upperIndex + 1) % this.vertices.length) {
|
||||||
|
//console.log("Case 1: Vertex("+i+"), lowerIndex("+lowerIndex+"), upperIndex("+upperIndex+"), poly.size("+this.vertices.length+")");
|
||||||
|
p[0] = (lowerInt[0] + upperInt[0]) / 2;
|
||||||
|
p[1] = (lowerInt[1] + upperInt[1]) / 2;
|
||||||
|
steinerPoints.push(p);
|
||||||
|
|
||||||
|
if (i < upperIndex) {
|
||||||
|
//lowerPoly.insert(lowerPoly.end(), poly.begin() + i, poly.begin() + upperIndex + 1);
|
||||||
|
lowerPoly.append(poly, i, upperIndex+1);
|
||||||
|
lowerPoly.vertices.push(p);
|
||||||
|
upperPoly.vertices.push(p);
|
||||||
|
if (lowerIndex != 0){
|
||||||
|
//upperPoly.insert(upperPoly.end(), poly.begin() + lowerIndex, poly.end());
|
||||||
|
upperPoly.append(poly,lowerIndex,poly.vertices.length);
|
||||||
|
}
|
||||||
|
//upperPoly.insert(upperPoly.end(), poly.begin(), poly.begin() + i + 1);
|
||||||
|
upperPoly.append(poly,0,i+1);
|
||||||
|
} else {
|
||||||
|
if (i != 0){
|
||||||
|
//lowerPoly.insert(lowerPoly.end(), poly.begin() + i, poly.end());
|
||||||
|
lowerPoly.append(poly,i,poly.vertices.length);
|
||||||
|
}
|
||||||
|
//lowerPoly.insert(lowerPoly.end(), poly.begin(), poly.begin() + upperIndex + 1);
|
||||||
|
lowerPoly.append(poly,0,upperIndex+1);
|
||||||
|
lowerPoly.vertices.push(p);
|
||||||
|
upperPoly.vertices.push(p);
|
||||||
|
//upperPoly.insert(upperPoly.end(), poly.begin() + lowerIndex, poly.begin() + i + 1);
|
||||||
|
upperPoly.append(poly,lowerIndex,i+1);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// connect to the closest point within the triangle
|
||||||
|
//console.log("Case 2: Vertex("+i+"), closestIndex("+closestIndex+"), poly.size("+this.vertices.length+")\n");
|
||||||
|
|
||||||
|
if (lowerIndex > upperIndex) {
|
||||||
|
upperIndex += this.vertices.length;
|
||||||
|
}
|
||||||
|
closestDist = Number.MAX_VALUE;
|
||||||
|
|
||||||
|
if(upperIndex < lowerIndex){
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var j = lowerIndex; j <= upperIndex; ++j) {
|
||||||
|
if (Point.leftOn(poly.at(i - 1), poly.at(i), poly.at(j))
|
||||||
|
&& Point.rightOn(poly.at(i + 1), poly.at(i), poly.at(j))) {
|
||||||
|
d = Point.sqdist(poly.at(i), poly.at(j));
|
||||||
|
if (d < closestDist) {
|
||||||
|
closestDist = d;
|
||||||
|
closestIndex = j % this.vertices.length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i < closestIndex) {
|
||||||
|
lowerPoly.append(poly,i,closestIndex+1);
|
||||||
|
if (closestIndex != 0){
|
||||||
|
upperPoly.append(poly,closestIndex,v.length);
|
||||||
|
}
|
||||||
|
upperPoly.append(poly,0,i+1);
|
||||||
|
} else {
|
||||||
|
if (i != 0){
|
||||||
|
lowerPoly.append(poly,i,v.length);
|
||||||
|
}
|
||||||
|
lowerPoly.append(poly,0,closestIndex+1);
|
||||||
|
upperPoly.append(poly,closestIndex,i+1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// solve smallest poly first
|
||||||
|
if (lowerPoly.vertices.length < upperPoly.vertices.length) {
|
||||||
|
lowerPoly.quickDecomp(result,reflexVertices,steinerPoints,delta,maxlevel,level);
|
||||||
|
upperPoly.quickDecomp(result,reflexVertices,steinerPoints,delta,maxlevel,level);
|
||||||
|
} else {
|
||||||
|
upperPoly.quickDecomp(result,reflexVertices,steinerPoints,delta,maxlevel,level);
|
||||||
|
lowerPoly.quickDecomp(result,reflexVertices,steinerPoints,delta,maxlevel,level);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result.push(this);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove collinear points in the polygon.
|
||||||
|
* @method removeCollinearPoints
|
||||||
|
* @param {Number} [precision] The threshold angle to use when determining whether two edges are collinear. Use zero for finest precision.
|
||||||
|
* @return {Number} The number of points removed
|
||||||
|
*/
|
||||||
|
Polygon.prototype.removeCollinearPoints = function(precision){
|
||||||
|
var num = 0;
|
||||||
|
for(var i=this.vertices.length-1; this.vertices.length>3 && i>=0; --i){
|
||||||
|
if(Point.collinear(this.at(i-1),this.at(i),this.at(i+1),precision)){
|
||||||
|
// Remove the middle point
|
||||||
|
this.vertices.splice(i%this.vertices.length,1);
|
||||||
|
i--; // Jump one point forward. Otherwise we may get a chain removal
|
||||||
|
num++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return num;
|
||||||
|
};
|
||||||
|
|
||||||
|
},{"./Line":1,"./Point":2,"./Scalar":4}],4:[function(require,module,exports){
|
||||||
|
module.exports = Scalar;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scalar functions
|
||||||
|
* @class Scalar
|
||||||
|
*/
|
||||||
|
function Scalar(){}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if two scalars are equal
|
||||||
|
* @static
|
||||||
|
* @method eq
|
||||||
|
* @param {Number} a
|
||||||
|
* @param {Number} b
|
||||||
|
* @param {Number} [precision]
|
||||||
|
* @return {Boolean}
|
||||||
|
*/
|
||||||
|
Scalar.eq = function(a,b,precision){
|
||||||
|
precision = precision || 0;
|
||||||
|
return Math.abs(a-b) < precision;
|
||||||
|
};
|
||||||
|
|
||||||
|
},{}],5:[function(require,module,exports){
|
||||||
|
module.exports = {
|
||||||
|
Polygon : require("./Polygon"),
|
||||||
|
Point : require("./Point"),
|
||||||
|
};
|
||||||
|
|
||||||
|
},{"./Point":2,"./Polygon":3}]},{},[5])
|
||||||
|
(5)
|
||||||
|
});
|
||||||
|
;
|
|
@ -563,6 +563,9 @@ var Body = {};
|
||||||
* @return {}
|
* @return {}
|
||||||
*/
|
*/
|
||||||
var _totalProperties = function(body) {
|
var _totalProperties = function(body) {
|
||||||
|
// https://ecourses.ou.edu/cgi-bin/ebook.cgi?doc=&topic=st&chap_sec=07.2&page=theory
|
||||||
|
// http://output.to/sideway/default.asp?qno=121100087
|
||||||
|
|
||||||
var properties = {
|
var properties = {
|
||||||
mass: 0,
|
mass: 0,
|
||||||
area: 0,
|
area: 0,
|
||||||
|
@ -576,10 +579,10 @@ var Body = {};
|
||||||
properties.mass += part.mass;
|
properties.mass += part.mass;
|
||||||
properties.area += part.area;
|
properties.area += part.area;
|
||||||
properties.inertia += part.inertia;
|
properties.inertia += part.inertia;
|
||||||
Vector.add(properties.centre, part.position, properties.centre);
|
properties.centre = Vector.add(properties.centre, Vector.mult(part.position, part.mass));
|
||||||
}
|
}
|
||||||
|
|
||||||
properties.centre = Vector.div(properties.centre, body.parts.length - 1);
|
properties.centre = Vector.div(properties.centre, properties.mass);
|
||||||
|
|
||||||
return properties;
|
return properties;
|
||||||
};
|
};
|
||||||
|
|
|
@ -93,7 +93,7 @@ var Bodies = {};
|
||||||
* @param {number} y
|
* @param {number} y
|
||||||
* @param {number} radius
|
* @param {number} radius
|
||||||
* @param {object} [options]
|
* @param {object} [options]
|
||||||
* @param {number} maxSides
|
* @param {number} [maxSides]
|
||||||
* @return {body} A new circle body
|
* @return {body} A new circle body
|
||||||
*/
|
*/
|
||||||
Bodies.circle = function(x, y, radius, options, maxSides) {
|
Bodies.circle = function(x, y, radius, options, maxSides) {
|
||||||
|
@ -161,4 +161,100 @@ var Bodies = {};
|
||||||
return Body.create(Common.extend({}, polygon, options));
|
return Body.create(Common.extend({}, polygon, options));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a body using the supplied vertices.
|
||||||
|
* If the vertices are not convex, they will be decomposed if [poly-decomp.js](https://github.com/schteppe/poly-decomp.js) is available.
|
||||||
|
* If the vertices can not be decomposed, the function will use the convex hull.
|
||||||
|
* By default the decomposition will discard collinear edges (to improve performance).
|
||||||
|
* The options parameter is an object that specifies any properties you wish to override the defaults.
|
||||||
|
* See the properties section of the `Matter.Body` module for detailed information on what you can pass via the `options` object.
|
||||||
|
* @method fromVertices
|
||||||
|
* @param {number} x
|
||||||
|
* @param {number} y
|
||||||
|
* @param [vector] vertices
|
||||||
|
* @param {object} [options]
|
||||||
|
* @param {bool} [removeCollinear=true]
|
||||||
|
* @return {body}
|
||||||
|
*/
|
||||||
|
Bodies.fromVertices = function(x, y, vertices, options, removeCollinear) {
|
||||||
|
var canDecompose = true,
|
||||||
|
body,
|
||||||
|
i;
|
||||||
|
|
||||||
|
options = options || {};
|
||||||
|
removeCollinear = typeof removeCollinear !== 'undefined' ? removeCollinear : true;
|
||||||
|
|
||||||
|
if (Vertices.isConvex(vertices)) {
|
||||||
|
// vertices are convex, so just create a body normally
|
||||||
|
body = {
|
||||||
|
position: { x: x, y: y },
|
||||||
|
vertices: vertices
|
||||||
|
};
|
||||||
|
|
||||||
|
return Body.create(Common.extend({}, body, options));
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for poly-decomp.js
|
||||||
|
if (!window.decomp) {
|
||||||
|
Common.log('Bodies.fromVertices: poly-decomp.js required. Could not decompose vertices. Fallback to convex hull.', 'warn');
|
||||||
|
canDecompose = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// initialise a decomposition
|
||||||
|
var concave = new decomp.Polygon();
|
||||||
|
for (i = 0; i < vertices.length; i++) {
|
||||||
|
concave.vertices.push([vertices[i].x, vertices[i].y]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for complexity
|
||||||
|
if (!concave.isSimple()) {
|
||||||
|
Common.log('Bodies.fromVertices: Non-simple polygons are not supported. Could not decompose vertices. Fallback to convex hull.', 'warn');
|
||||||
|
canDecompose = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// try to decompose
|
||||||
|
if (canDecompose) {
|
||||||
|
// vertices are concave and simple, we can decompose into parts
|
||||||
|
concave.makeCCW();
|
||||||
|
if (removeCollinear)
|
||||||
|
concave.removeCollinearPoints(0.001);
|
||||||
|
|
||||||
|
var decomposed = concave.quickDecomp(),
|
||||||
|
parts = [];
|
||||||
|
|
||||||
|
// for each decomposed chunk
|
||||||
|
for (i = 0; i < decomposed.length; i++) {
|
||||||
|
var chunk = decomposed[i],
|
||||||
|
chunkVertices = [];
|
||||||
|
|
||||||
|
// convert vertices into the correct structure
|
||||||
|
for (var j = 0; j < chunk.vertices.length; j++) {
|
||||||
|
chunkVertices.push({ x: chunk.vertices[j][0], y: chunk.vertices[j][1] });
|
||||||
|
}
|
||||||
|
|
||||||
|
// create a compound part
|
||||||
|
parts.push(
|
||||||
|
Body.create({
|
||||||
|
position: Vertices.centre(chunkVertices),
|
||||||
|
vertices: chunkVertices
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// create the parent body to be returned, that contains generated compound parts
|
||||||
|
body = Body.create(Common.extend({}, { parts: parts }, options));
|
||||||
|
Body.setPosition(body, { x: x, y: y });
|
||||||
|
|
||||||
|
return body;
|
||||||
|
} else {
|
||||||
|
// fallback to convex hull when decomposition is not possible
|
||||||
|
body = {
|
||||||
|
position: { x: x, y: y },
|
||||||
|
vertices: Vertices.hull(vertices)
|
||||||
|
};
|
||||||
|
|
||||||
|
return Body.create(Common.extend({}, body, options));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
|
|
@ -56,7 +56,7 @@ var Vertices = {};
|
||||||
* @return {vertices} vertices
|
* @return {vertices} vertices
|
||||||
*/
|
*/
|
||||||
Vertices.fromPath = function(path, body) {
|
Vertices.fromPath = function(path, body) {
|
||||||
var pathPattern = /L\s*([\-\d\.]*)\s*([\-\d\.]*)/ig,
|
var pathPattern = /L?\s*([\-\d\.]+)\s*([\-\d\.]+)\s*,?/ig,
|
||||||
points = [];
|
points = [];
|
||||||
|
|
||||||
path.replace(pathPattern, function(match, x, y) {
|
path.replace(pathPattern, function(match, x, y) {
|
||||||
|
@ -330,15 +330,58 @@ var Vertices = {};
|
||||||
* @return {vertices} vertices
|
* @return {vertices} vertices
|
||||||
*/
|
*/
|
||||||
Vertices.clockwiseSort = function(vertices) {
|
Vertices.clockwiseSort = function(vertices) {
|
||||||
var mean = Vertices.mean(vertices);
|
var centre = Vertices.mean(vertices);
|
||||||
|
|
||||||
vertices.sort(function(vertexA, vertexB) {
|
vertices.sort(function(vertexA, vertexB) {
|
||||||
return Vector.angle(mean, vertexA) - Vector.angle(mean, vertexB);
|
return Vector.angle(centre, vertexA) - Vector.angle(centre, vertexB);
|
||||||
});
|
});
|
||||||
|
|
||||||
return vertices;
|
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/
|
||||||
|
|
||||||
|
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.
|
* Returns the convex hull of the input vertices as a new array of points.
|
||||||
* @method hull
|
* @method hull
|
||||||
|
|
Loading…
Reference in a new issue