math/Rectangle.js

import { lineIntersectsLine } from "./Utils.js";

/** 
 * Rectangle class
 * TODO maybe look at https://dxr.mozilla.org/mozilla-beta/source/toolkit/modules/Geometry.jsm
 */
class Rectangle {
    /** 
     * @param {number} x
     * @param {number} y
     * @param {number} w width
     * @param {number} h height
     */
    constructor(x, y, w, h) {
        this.x = x;
        this.y = y;
        this.w = w;
        this.h = h;
    }

    /**
     * @readonly
     */
    get x1() {
        return this.x;
    }

    /**
     * @readonly
     */
    get y1() {
        return this.y;
    }

    /**
     * @readonly
     */
    get x2() {
        return this.x + this.w;
    }

    /**
     * @readonly
     */
    get y2() {
        return this.y + this.h;
    }

    set x1(v) {
        this.x = v;
    }

    set y1(v) {
        this.y = v;
    }

    set x2(v) {
        this.w = v - this.x;
    }

    set y2(v) {
        this.h = v - this.y;
    }

    /**
     * @readonly
     */
    get center() {
        return {
            x: this.x + this.w * 0.5,
            y: this.y + this.h * 0.5,
        };
    }

    /**
     * clone this instance
     * @return {Rectangle} 
     */
    clone() {
        return new Rectangle(this.x, this.y, this.w, this.h);
    }

    /**
     * shrink the rectangle from each side by amount x on the x-axis and amount y on the y-axis
     * if y is null, y is set to the value of x
     * @param {number} x
     * @param {number} [y=null]
     * @return {Rectangle} 
     */
    shrink(x, y = null) {
        if (y == null) y = x;

        this.x += x;
        this.y += y;
        this.w -= x * 2;
        this.h -= y * 2;
        return this;
    }

    /**
     * multiply position and size of the rectangle by amount x on the x-axis and amount y on the y-axis
     * if y is null, y is set to the value of x
     * @param {number} x
     * @param {number} [y=null]
     * @return {Rectangle} 
     */
    multiply(x, y = null) {
        if (y == null) y = x;
        this.x *= x;
        this.y *= y;
        this.w *= x;
        this.h *= y;
        return this;
    }

    /**
     * multiply the size of the rectangle by amount x on the x-axis and amount y on the y-axis
     * if y is null, y is set to the value of x
     * @param {number} x
     * @param {number} [y=null]
     * @return {Rectangle} 
     */
    scale(x, y = null) {
        if (y == null) y = x;
        this.w *= x;
        this.h *= y;
        return this;
    }

    /**
     * Check if the given Point (x,y) is inside the rectangle, padding is added in each direction
     * @param {number} x
     * @param {number} y
     * @param {number} pad padding
     * @return {boolean} 
     */
    contains(x, y, pad = 0) {
        return x > this.x1 - pad && x < this.x2 + pad && y > this.y1 - pad && y < this.y2 + pad;
    }

    /**
     * get the lines of this rectangle as a nested array of line-points, clockwise
     * e.g. [[[0,0],[10,0]], [[10,0],[10,10]], [[10,10],[0,10]], [[0,10],[0,0]]]
     * @return {Array} 
     */
    getLines() {
        return [
            this.getTopLine(),
            this.getRightLine(),
            this.getBottomLine(),
            this.getLeftLine()
        ];
    }

    getTopLine() {
        return [
            [this.x1, this.y1],
            [this.x2, this.y1]
        ]
    }

    getBottomLine() {
        return [
            [this.x2, this.y2],
            [this.x1, this.y2]
        ]
    }

    getLeftLine() {
        return [
            [this.x1, this.y2],
            [this.x1, this.y1]
        ]
    }

    getRightLine() {
        return [
            [this.x2, this.y1],
            [this.x2, this.y2]
        ]
    }

    /**
     * check if this rectangle intersects or contains the given line from Point (x1,y1) to Point (x2, y2)
     *
     * @param {number} x1
     * @param {number} y1
     * @param {number} x2
     * @param {number} y2
     * @param {number} pad padding
     * @return {boolean} 
     */
    intersectsLine(x1, y1, x2, y2, pad = 0) {
        return lineIntersectsLine(x1, y1, x2, y2, this.x1 - pad, this.y1 - pad, this.x1 - pad, this.y2 + pad) ||
            lineIntersectsLine(x1, y1, x2, y2, this.x2 + pad, this.y1 - pad, this.x2 + pad, this.y2 + pad) ||
            lineIntersectsLine(x1, y1, x2, y2, this.x1 - pad, this.y1 - pad, this.x2 + pad, this.y1 - pad) ||
            lineIntersectsLine(x1, y1, x2, y2, this.x1 - pad, this.y2 + pad, this.x2 + pad, this.y2 + pad) ||
            this.contains(x1, y1, pad) ||
            this.contains(x2, y2, pad);
    }

    /**
     * check if this rectangle intersects the given Rectangle
     *
     * @param {Rectangle} other
     * @return {boolean} 
     */
    intersectsRect(other) {
        let x1 = Math.max(this.x1, other.x1);
        let x2 = Math.min(this.x2, other.x2);
        let y1 = Math.max(this.y1, other.y1);
        let y2 = Math.min(this.y2, other.y2);
        return x1 < x2 && y1 < y2;
    }
}

export default Rectangle;