ridder
Version:
A straightforward game engine for simple data-driven games in JavaScript
183 lines (182 loc) • 6.17 kB
JavaScript
import { doLinesIntersect } from "./line.js";
import { toRadians } from "./utils.js";
import { cloneVector, vec } from "./vector.js";
/**
* Create a new polygon data structure.
* @param x - The x-coordinate.
* @param y - The y-coordinate.
* @param points - An array of {@link Vector} points to create the convex polygon.
*/
export function polygon(x = 0, y = 0, points = []) {
return {
x,
y,
basePoints: points,
calcPoints: points.map(cloneVector),
};
}
/**
* Create a new polygon from a rectangle.
* @param rx - The x-coordinate of the rectangle.
* @param ry - The y-coordinate of the rectangle.
* @param rw - The width of the rectangle.
* @param rh - The height of the rectangle
*/
export function polygonFromRect(x, y, rx, ry, rw, rh) {
return polygon(x, y, getPointsFromRect(rx, ry, rw, rh));
}
/**
* Create a new polygon from a circle.
* @param cx - The x-coordinate of the circle.
* @param cy - The y-coordinate of the circle.
* @param cr - The radius of the circle.
* @param segments - The number of segments to create the polygon, e.g. 3 creates a triangle.
*/
export function polygonFromCircle(x, y, cx, cy, cr, segments) {
return polygon(x, y, getPointsFromCircle(cx, cy, cr, segments));
}
/**
* Set the components of the polygon and returns the polygon.
*/
export function setPolygon(p, x, y, points) {
p.x = x;
p.y = y;
p.basePoints = points.map(cloneVector);
p.calcPoints = points.map(cloneVector);
return p;
}
/**
* Set the components of the polygon from a rectangle and returns the polygon.
* @param rx - The x-coordinate of the rectangle.
* @param ry - The y-coordinate of the rectangle.
* @param rw - The width of the rectangle.
* @param rh - The height of the rectangle
*/
export function setPolygonFromRect(p, x, y, rx, ry, rw, rh) {
const points = getPointsFromRect(rx, ry, rw, rh);
p.x = x;
p.y = y;
p.basePoints = points;
p.calcPoints = points.map(cloneVector);
}
/**
* Set the components of the polygon from a circle and returns the polygon.
* @param cx - The x-coordinate of the circle.
* @param cy - The y-coordinate of the circle.
* @param cr - The radius of the circle.
* @param segments - The number of segments to create the polygon, e.g. 3 creates a triangle.
*/
export function setPolygonFromCircle(p, x, y, cx, cy, cr, segments) {
const points = getPointsFromCircle(cx, cy, cr, segments);
p.x = x;
p.y = y;
p.basePoints = points;
p.calcPoints = points.map(cloneVector);
}
/**
* Returns an array of points to create a polygon from a rectangle.
* @param x - The x-coordinate of the rectangle.
* @param y - The y-coordinate of the rectangle.
* @param w - The width of the rectangle.
* @param h - The height of the rectangle.
*/
function getPointsFromRect(x, y, w, h) {
return [vec(x, y), vec(x + w, y), vec(x + w, y + h), vec(x, y + h)];
}
/**
* Returns an array of points to create a polygon from a circle.
* @param x - The x-coordinate of the circle.
* @param y - The y-coordinate of the circle.
* @param r - The radius of the circle.
* @param segments - The number of segments to create the polygon, e.g. 3 creates a triangle.
*/
function getPointsFromCircle(x, y, r, segments) {
const points = [];
const step = 360 / segments;
for (let degrees = 0; degrees < 360; degrees += step) {
const radians = toRadians(degrees);
const pointX = x + Math.cos(radians) * r;
const pointY = y + Math.sin(radians) * r;
points.push(vec(pointX, pointY));
}
return points;
}
/**
* Returns `true` when the polygon has 3 or more points.
*/
export function isPolygonValid(p) {
return p.basePoints.length >= 3;
}
/**
* Rotate the polygon to the given angle in degrees.
*/
export function setPolygonAngle(p, angle) {
const radians = toRadians(angle);
for (let i = 0; i < p.basePoints.length; i++) {
const base = p.basePoints[i];
const calc = p.calcPoints[i];
const rotatedX = base.x * Math.cos(radians) - base.y * Math.sin(radians);
const rotatedY = base.x * Math.sin(radians) + base.y * Math.cos(radians);
calc.x = rotatedX;
calc.y = rotatedY;
}
}
/**
* Returns `true` when the polygons {@link a} and {@link b} intersect (overlap).
*/
export function doPolygonsIntersect(a, b) {
if (a === b || !isPolygonValid(a) || !isPolygonValid(b)) {
return false;
}
if (doesPolygonContain(a, b.x, b.y) || doesPolygonContain(b, a.x, a.y)) {
return true;
}
for (let i = 0; i < a.calcPoints.length; i++) {
const p1 = a.calcPoints[i];
const p2 = a.calcPoints[(i + 1) % a.calcPoints.length];
const x1 = p1.x + a.x;
const y1 = p1.y + a.y;
const x2 = p2.x + a.x;
const y2 = p2.y + a.y;
for (let j = 0; j < b.calcPoints.length; j++) {
const p3 = b.calcPoints[j];
const p4 = b.calcPoints[(j + 1) % b.calcPoints.length];
const x3 = p3.x + b.x;
const y3 = p3.y + b.y;
const x4 = p4.x + b.x;
const y4 = p4.y + b.y;
if (doLinesIntersect(x1, y1, x2, y2, x3, y3, x4, y4)) {
return true;
}
}
}
return false;
}
/**
* Returns `true` when {@link x} and {@link y} are inside the polygon {@link p}.
*/
export function doesPolygonContain(p, x, y) {
let crossings = 0;
for (let i = 0; i < p.calcPoints.length; i++) {
const a = p.calcPoints[i];
const b = p.calcPoints[(i + 1) % p.calcPoints.length];
const x1 = a.x + p.x;
const y1 = a.y + p.y;
const x2 = b.x + p.x;
const y2 = b.y + p.y;
if (doLinesIntersect(x, y, Number.MAX_SAFE_INTEGER, y, x1, y1, x2, y2)) {
crossings++;
}
}
return crossings % 2 === 1;
}
/**
* Copy the data components of polygon {@link b} into polygon {@link a} and returns polygon {@link a}.
*/
export function copyPolygon(a, b) {
a.x = b.x;
a.y = b.y;
a.basePoints = b.basePoints.map(cloneVector);
a.calcPoints = b.calcPoints.map(cloneVector);
return a;
}