lettuce
Version:
Lettuce JS, Mini Mobile Framework for Romantic with DSL.
304 lines (280 loc) • 10.4 kB
JavaScript
/*
* MelonJS Game Engine
* Copyright (C) 2011 - 2014 Olivier Biot, Jason Oster, Aaron McLeod
* http://www.melonjs.org
*
*/
(function () {
/**
* a polygon Object.<br>
* Please do note that melonJS implements a simple Axis-Aligned Boxes collision algorithm, which requires all polygons used for collision to be convex with all vertices defined with clockwise winding.
* A polygon is convex when all line segments connecting two points in the interior do not cross any edge of the polygon
* (which means that all angles are less than 180 degrees), as described here below : <br>
* <center><img src="images/convex_polygon.png"/></center><br>
* A polygon's `winding` is clockwise iff its vertices (points) are declared turning to the right. The image above shows COUNTERCLOCKWISE winding.
* @class
* @extends Object
* @memberOf me
* @constructor
* @param {Number} x origin point of the Polygon
* @param {Number} y origin point of the Polygon
* @param {me.Vector2d[]} points array of vector defining the Polygon
*/
me.Polygon = Object.extend(
/** @scope me.Polygon.prototype */ {
/** @ignore */
init : function (x, y, points) {
/**
* origin point of the Polygon
* @public
* @type {me.Vector2d}
* @name pos
* @memberOf me.Polygon
*/
this.pos = new me.Vector2d();
/**
* The bounding rectangle for this shape
* @protected
* @type {me.Rect}
* @name bounds
* @memberOf me.Polygon
*/
this.bounds = undefined;
/**
* Array of points defining the Polygon <br>
* Note: If you manually change `points`, you **must** call `recalc`afterwards so that the changes get applied correctly.
* @public
* @type {me.Vector2d[]}
* @name points
* @memberOf me.Polygon
*/
this.points = null;
// the shape type
this.shapeType = "Polygon";
this.setShape(x, y, points);
},
/**
* set new value to the Polygon
* @name setShape
* @memberOf me.Polygon
* @function
* @param {Number} x position of the Polygon
* @param {Number} y position of the Polygon
* @param {me.Vector2d[]} points array of vector defining the Polygon
*/
setShape : function (x, y, points) {
this.pos.set(x, y);
this.points = points;
this.recalc();
this.updateBounds();
return this;
},
/**
* Rotate this Polygon (counter-clockwise) by the specified angle (in radians).
* @name rotate
* @memberOf me.Polygon
* @function
* @param {Number} angle The angle to rotate (in radians)
* @return {me.Polygon} Reference to this object for method chaining
*/
rotate : function (angle) {
if (angle !== 0) {
var points = this.points;
var len = points.length;
for (var i = 0; i < len; i++) {
points[i].rotate(angle);
}
this.recalc();
this.updateBounds();
}
return this;
},
/**
* Scale this Polygon by the given scalar.
* @name scale
* @memberOf me.Polygon
* @function
* @param {Number} x
* @param {Number} [y=x]
* @return {me.Polygon} Reference to this object for method chaining
*/
scale : function (x, y) {
y = typeof (y) !== "undefined" ? y : x;
var points = this.points;
var len = points.length;
for (var i = 0; i < len; i++) {
points[i].scale(x, y);
}
this.recalc();
this.updateBounds();
return this;
},
/**
* Scale this Polygon by the given vector
* @name scaleV
* @memberOf me.Polygon
* @function
* @param {me.Vector2d} v
* @return {me.Polygon} Reference to this object for method chaining
*/
scaleV : function (v) {
return this.scale(v.x, v.y);
},
/**
* Computes the calculated collision polygon.
* This **must** be called if the `points` array, `angle`, or `offset` is modified manually.
* @name recalc
* @memberOf me.Polygon
* @function
*/
recalc : function () {
var i;
// The edges here are the direction of the `n`th edge of the polygon, relative to
// the `n`th point. If you want to draw a given edge from the edge value, you must
// first translate to the position of the starting point.
var edges = this.edges = [];
// The normals here are the direction of the normal for the `n`th edge of the polygon, relative
// to the position of the `n`th point. If you want to draw an edge normal, you must first
// translate to the position of the starting point.
var normals = this.normals = [];
// Copy the original points array and apply the offset/angle
var points = this.points;
var len = points.length;
if (len < 3) {
throw new me.Polygon.Error("Requires at least 3 points");
}
// Calculate the edges/normals
for (i = 0; i < len; i++) {
var e = new me.Vector2d().copy(points[(i + 1) % len]).sub(points[i]);
edges.push(e);
normals.push(new me.Vector2d().copy(e).perp().normalize());
}
return this;
},
/**
* translate the Polygon by the specified offset
* @name translate
* @memberOf me.Polygon
* @function
* @param {Number} x x offset
* @param {Number} y y offset
* @return {me.Polygon} this Polygon
*/
translate : function (x, y) {
this.pos.x += x;
this.pos.y += y;
this.bounds.translate(x, y);
return this;
},
/**
* translate the Polygon by the specified vector
* @name translateV
* @memberOf me.Polygon
* @function
* @param {me.Vector2d} v vector offset
* @return {me.Polygon} this Polygon
*/
translateV : function (v) {
this.pos.add(v);
this.bounds.translateV(v);
return this;
},
/**
* check if this Polygon contains the specified point
* @name containsPointV
* @memberOf me.Polygon
* @function
* @param {me.Vector2d} point
* @return {boolean} true if contains
*/
containsPointV: function (v) {
return this.containsPoint(v.x, v.y);
},
/**
* check if this Polygon contains the specified point <br>
* (Note: it is highly recommended to first do a hit test on the corresponding <br>
* bounding rect, as the function can be highly consuming with complex shapes)
* @name containsPoint
* @memberOf me.Polygon
* @function
* @param {Number} x x coordinate
* @param {Number} y y coordinate
* @return {boolean} true if contains
*/
containsPoint: function (x, y) {
var intersects = false;
var posx = this.pos.x, posy = this.pos.y;
var points = this.points;
var len = points.length;
//http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html
for (var i = 0, j = len - 1; i < len; j = i++) {
var iy = points[i].y + posy, ix = points[i].x + posx,
jy = points[j].y + posy, jx = points[j].x + posx;
if (((iy > y) !== (jy > y)) && (x < (jx - ix) * (y - iy) / (jy - iy) + ix)) {
intersects = !intersects;
}
}
return intersects;
},
/**
* returns the bounding box for this shape, the smallest Rectangle object completely containing this shape.
* @name getBounds
* @memberOf me.Polygon
* @function
* @return {me.Rect} this shape bounding box Rectangle object
*/
getBounds : function () {
return this.bounds;
},
/**
* update the bounding box for this shape.
* @name updateBounds
* @memberOf me.Polygon
* @function
* @return {me.Rect} this shape bounding box Rectangle object
*/
updateBounds : function () {
var x = Infinity, y = Infinity, right = -Infinity, bottom = -Infinity;
this.points.forEach(function (point) {
x = Math.min(x, point.x);
y = Math.min(y, point.y);
right = Math.max(right, point.x);
bottom = Math.max(bottom, point.y);
});
if (!this.bounds) {
this.bounds = new me.Rect(x, y, right - x, bottom - y);
} else {
this.bounds.setShape(x, y, right - x, bottom - y);
}
return this.bounds.translateV(this.pos);
},
/**
* clone this Polygon
* @name clone
* @memberOf me.Polygon
* @function
* @return {me.Polygon} new Polygon
*/
clone : function () {
var copy = [];
this.points.forEach(function (point) {
copy.push(new me.Vector2d(point.x, point.y));
});
return new me.Polygon(this.pos.x, this.pos.y, copy);
}
});
/**
* Base class for Polygon exception handling.
* @name Error
* @class
* @memberOf me.Polygon
* @constructor
* @param {String} msg Error message.
*/
me.Polygon.Error = me.Error.extend({
init : function (msg) {
this._super(me.Error, "init", [ msg ]);
this.name = "me.Polygon.Error";
}
});
})();