👔Rect

customclass ⟩ Rect

an object that has origin (x, y) and size (width, height).

👉 custom objects

// (Rect 1.1)
// 2023.01.22 - 22:57 (+) .points    
//                    (+) .topRight, .topLeft, .top, .bottom, .left, .right
// 2023.01.22 - 20:18 (+) .translate()
// 2023.01.21 - 13:38 (+) .inset()
// 2023.01.18 - 14:33 (+) .toString()
// 2023.01.18 - 09:01 (/) refactor rect()
// 2023.01.17 - 22:19 (•) first draft
// -------------------------------------------------

const {min, abs} = Math;

// ⭐️ import
// -------------------------------------------------
import {vec} from './Vector.js';        // 👔 Vector
import {Size} from './Size.js';         // 👔 Size

// ⭐️ Rect
// -------------------------------------------------
// - new Rect(x, y, w, h)
// - rect(x, y, w, h)
// - rect(corner, offset)
// - rect(center, offset, true)
// -------------------------------------------------
// 🔸 .x, .y, .width, .height
// 🔸 .coords                    - [x, y, w, h]
// 🔸 .size
// -------------------------------------------------
// 🔸 .origin, .offset           - vectors
// 🔸 .center, .bottomLeft, .bottomRight
// 🔸 .bottomLeft, .bottomRight, .topLeft, .topRight
// 🔸 .top
// 🔸 .points
// -------------------------------------------------
// .halfWidth, .halfHeight, .halfOffset
// -------------------------------------------------
// 🔹 .inset()
// 🔹 .translate()
// -------------------------------------------------
// 🔹 .toString()
// 

class Rect {

    // init
    constructor(
        x, y,            // a corner point, not necessarily the top-left corner
        width, height    // a "vector" pointing to the opposite corner
    ) {
        // top-left corner
        this.x = min(x, x + width);
        this.y = min(y, y + height);
        // always positive width/height
        this.width  = abs(width);
        this.height = abs(height);
    }

    // ------------------------
    //     size related
    // ------------------------

    // 🔸 .size
    get size() { 
        return new Size(this.width, this.height)         // 👔 Size
    }

    // 🔸 .halfWidth, .halfHeight
    get halfWidth()  { return this.width / 2 }
    get halfHeight() { return this.height / 2 }

    // ------------------------
    //     vectors / points
    // ------------------------

    // 🔸 .origin (topLeft)
    get origin() { return vec(this.x, this.y) }            // 👔 Vector
    get topLeft() { return this.origin } 
    
    // 🔸 .origin, .offset, .halfOffset
    get offset() { return vec(this.width, this.height) }   // 👔 Vector
    get halfOffset() { return this.offset.times(1/2) }     // 👔 Vector
    
    // 🔸 .center
    get center() { return this.origin.plus(this.halfOffset) }
    
    // 🔸 .bottomLeft
    get bottomLeft() { 
        const {halfWidth: w, halfHeight: h} = this;        // 👔 Vector
        return this.center.plus(-w, h);
    }

    // 🔸 .bottomRight
    get bottomRight() { 
        return this.origin.plus(this.offset);              // 👔 Vector
    }

    // 🔸 .topRight
    get topRight() { 
        const {halfWidth: w, halfHeight: h} = this;        // 👔 Vector
        return this.center.plus(w, -h);
    }

    // 🔸 .top
    get top() { 
        return this.center.plus(0, -this.halfHeight);
    }

    // 🔸 .bottom
    get bottom() { 
        return this.center.plus(0, this.halfHeight);
    }

    // 🔸 .left
    get left() { 
        return this.center.plus(-this.halfWidth, 0);
    }

    // 🔸 .right
    get right() { 
        return this.center.plus(this.halfWidth, 0);
    }

    // 🔸 .points
    get points() { 
        
        const {
            bottomLeft: p0, bottomRight: p1,
            topRight: p2, topLeft: p3,
        } = this;
        
        return [p0, p1, p2, p3];
    }

    // ------------------------
    //     rect operations
    // ------------------------

    // 🔹 .inset()
    inset(dx, dy = dx) {
        const v = vec(dx, dy);
        // new origin: o + v
        const origin = this.origin.plus(v);
        // new corner: o + diag - v     (diag = this.offset)
        // new offset = (o + diag - v) - (o + v) = diag - 2v
        const offset = this.offset.plus(v.times(-2));
        return rect(origin, offset);
    }

    // 🔹 .translate()
    translate(...args) {
        const v = vec(...args);    // vec(v) or vec(x, y)
        return rect(this.origin.plus(v), this.offset);
    }

    // ------------------------
    //     helpers / debug
    // ------------------------

    // rect.coords
    get coords() {
        const {x, y, width, height} = this;
        return [x, y, width, height];
    }

    // 🔹 .toString()
    toString() {
        return `(${this.coords})`;
    }
    
}

// convenience factory functions

// 🔸 rect()
function rect(...args) { 
    const len = args.length;
    switch (len) {
            
        // treat as (x, y, width, height)
        case 4: return new Rect(...args);
            
        // treat as (point, offset, center?)
        case 2:
        case 3:
            const [point, offset, center] = args;
            // `point` is corner
            if (!center) return new Rect(...point.coords, ...offset.coords);
            // `point` is center
            const corner = point.plus(offset);
            const offsetToOppositeCorner = offset.times(-2);
            return rect(corner, offsetToOppositeCorner);
            
        default:
            const msg = [
                `❌ rect(${args})`,
                `• expecting 2/3/4 arguments, but got ${len}.`,
                ``,
                `💡 rect() syntax:`,
                `------------------`,
                `• rect(x, y, width, height)`,
                `• rect(corner, offset): "corner" and "offset" are vectors.`,
                `• rect(center, offset, true): "true" means "center" is a center point.`,
            ];
            throw new Error(msg.join('\n'));
    }
    
}

// export
export {Rect, rect};    // ES module export

History

0: (?) first recorded
1: (+) refactor, .toString()
2: (+) .inset()

Last updated