source/modules/Polygon.js
const Body = require('./Body')
/**
* A polygon used to detect collisions
* @class
*/
class Polygon extends Body {
/**
* @constructor
* @param {Number} [x = 0] The starting X coordinate
* @param {Number} [y = 0] The starting Y coordinate
* @param {Array<Number[]>} [points = []] An array of coordinate pairs making up the polygon - [[x1, y1], [x2, y2], ...]
* @param {Number} [angle = 0] The starting rotation in radians
* @param {Number} [scale_x = 1] The starting scale along the X axis
* @param {Number} [scale_y = 1] The starting scale long the Y axis
* @param {Number} [padding = 0] The amount to pad the bounding volume when testing for potential collisions
*/
constructor (x = 0, y = 0, points = [], angle = 0, scale_x = 1, scale_y = 1, padding = 0) {
super(x, y, padding)
/**
* @desc The angle of the body in radians
* @type {Number}
*/
this.angle = angle
/**
* @desc The scale of the body along the X axis
* @type {Number}
*/
this.scale_x = scale_x
/**
* @desc The scale of the body along the Y axis
* @type {Number}
*/
this.scale_y = scale_y
/** @private */
this._polygon = true
/** @private */
this._x = x
/** @private */
this._y = y
/** @private */
this._angle = angle
/** @private */
this._scale_x = scale_x
/** @private */
this._scale_y = scale_y
/** @private */
this._min_x = 0
/** @private */
this._min_y = 0
/** @private */
this._max_x = 0
/** @private */
this._max_y = 0
/** @private */
this._points = null
/** @private */
this._coords = null
/** @private */
this._edges = null
/** @private */
this._normals = null
/** @private */
this._dirty_coords = true
/** @private */
this._dirty_normals = true
Polygon.prototype.setPoints.call(this, points)
}
/**
* Draws the polygon to a CanvasRenderingContext2D's current path
* @param {CanvasRenderingContext2D} context The context to add the shape to
*/
draw (context) {
if (
this._dirty_coords ||
this.x !== this._x ||
this.y !== this._y ||
this.angle !== this._angle ||
this.scale_x !== this._scale_x ||
this.scale_y !== this._scale_y
) {
this._calculateCoords()
}
const coords = this._coords
if (coords.length === 2) {
context.moveTo(coords[0], coords[1])
context.arc(coords[0], coords[1], 1, 0, Math.PI * 2)
} else {
context.moveTo(coords[0], coords[1])
for (let i = 2; i < coords.length; i += 2) {
context.lineTo(coords[i], coords[i + 1])
}
if (coords.length > 4) {
context.lineTo(coords[0], coords[1])
}
}
}
/**
* Sets the points making up the polygon. It's important to use this function when changing the polygon's shape to ensure internal data is also updated.
* @param {Array<Number[]>} new_points An array of coordinate pairs making up the polygon - [[x1, y1], [x2, y2], ...]
*/
setPoints (new_points) {
const count = new_points.length
this._points = new Float64Array(count * 2)
this._coords = new Float64Array(count * 2)
this._edges = new Float64Array(count * 2)
this._normals = new Float64Array(count * 2)
const points = this._points
for (let i = 0, ix = 0, iy = 1; i < count; ++i, ix += 2, iy += 2) {
const new_point = new_points[i]
points[ix] = new_point[0]
points[iy] = new_point[1]
}
this._dirty_coords = true
}
/**
* Calculates and caches the polygon's world coordinates based on its points, angle, and scale
*/
_calculateCoords () {
const x = this.x
const y = this.y
const angle = this.angle
const scale_x = this.scale_x
const scale_y = this.scale_y
const points = this._points
const coords = this._coords
const count = points.length
let min_x
let max_x
let min_y
let max_y
for (let ix = 0, iy = 1; ix < count; ix += 2, iy += 2) {
let coord_x = points[ix] * scale_x
let coord_y = points[iy] * scale_y
if (angle) {
const cos = Math.cos(angle)
const sin = Math.sin(angle)
const tmp_x = coord_x
const tmp_y = coord_y
coord_x = tmp_x * cos - tmp_y * sin
coord_y = tmp_x * sin + tmp_y * cos
}
coord_x += x
coord_y += y
coords[ix] = coord_x
coords[iy] = coord_y
if (ix === 0) {
min_x = max_x = coord_x
min_y = max_y = coord_y
} else {
if (coord_x < min_x) {
min_x = coord_x
} else if (coord_x > max_x) {
max_x = coord_x
}
if (coord_y < min_y) {
min_y = coord_y
} else if (coord_y > max_y) {
max_y = coord_y
}
}
}
this._x = x
this._y = y
this._angle = angle
this._scale_x = scale_x
this._scale_y = scale_y
this._min_x = min_x
this._min_y = min_y
this._max_x = max_x
this._max_y = max_y
this._dirty_coords = false
this._dirty_normals = true
}
/**
* Calculates the normals and edges of the polygon's sides
*/
_calculateNormals () {
const coords = this._coords
const edges = this._edges
const normals = this._normals
const count = coords.length
for (let ix = 0, iy = 1; ix < count; ix += 2, iy += 2) {
const next = ix + 2 < count ? ix + 2 : 0
const x = coords[next] - coords[ix]
const y = coords[next + 1] - coords[iy]
const length = x || y ? Math.sqrt(x * x + y * y) : 0
edges[ix] = x
edges[iy] = y
normals[ix] = length ? y / length : 0
normals[iy] = length ? -x / length : 0
}
this._dirty_normals = false
}
};
module.exports = Polygon
module.exports.default = module.exports