👔Rect
Last updated
Last updated
an object that has origin (x, y) and size (width, height).
replit ⟩ Rect (v1.1) , require -> Vector, Size
// (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
const { PI } = Math;
const { log } = console;
function deg(x) { return PI * x / 180 } // degrees
// ⭐️ import
import {vec, polar} from './js/Vector.js'; // Vector
import {rect} from './js/Rect.js'; // Rect
// 🔸 ctx.polygon(): regular polygon (subpath)
//
// parameters:
// - n : number of sides
// - x, y: center
// - r : radius
//
// require:
// - Vector, vec(), polar()
CanvasRenderingContext2D.prototype.polygon = function(n, x, y, r, {
startAngle = -Math.PI / 2, // starting angle
clockwise = true, // clockwise by default
} = {}) {
const center = vec(x, y); // center of polygon
const delta = 2 * Math.PI / n * (clockwise ? 1 : -1); // dθ = ± 2π / n
// begin new subpath (at first vertex)
this.moveTo(...center.plus(polar(r, startAngle)).coords);
// connect first (n-1) sides
for (let i = 1; i < n; i++) {
this.lineTo(...center.plus(polar(r, startAngle + delta * i)).coords);
}
// connect back to first vertex
this.closePath();
};
// ⭐ draw on <canvas> 2D context
function drawOnCanvas2D(selector, draw) {
let canvas = document.querySelector(selector);
if (!canvas) { console.log(`⛔ no such element: "${selector}"`); return; }
let context = canvas.getContext("2d");
if (draw) draw(context);
}
// ------------------
// main
// ------------------
// draw on canvas
drawOnCanvas2D('#playground', (c) => {
// drawing settings
const center = vec(200, 225); // triangle center
const R = 200, r = 80; // radii (large/small)
// text
const text = "<canvas>";
const textHeight = (R - r)/2;
c.font = `bold ${textHeight}pt sans-serif`; // Big font
const textWidth = c.measureText(text).width;
const textCenter = center.plus(0, R / 2 - R / 16);
const textRect = rect(textCenter, vec(textWidth/2, textHeight/2), {center: true});
log(`text size: ${textRect.size}`);
c.strokeRect(...textRect.coords);
c.strokeText(text, ...textRect.bottomLeft.coords);
// vertical stripe down the middle
const stripCenter = center.plus(0, -R/4); // center of triangle height
const stripHalfDimension = vec(40, .85*R);
const strip = rect(stripCenter, stripHalfDimension, {center: true});
c.strokeRect(...strip.coords);
// create path for clipping region
c.polygon(3, ...center.coords, R);
c.polygon(3, ...center.coords, r, { clockwise: false });
// ⭐ make that path the clipping region
c.clip();
c.fillStyle = 'hsl(0 80% 10% / 0.05)';
c.fill();
c.stroke(); // ⭐ half of line width will be clipped away
// fill strip (inside the clipping region)
c.fillStyle = "hsl(270 80% 50% / 0.8)";
c.fillRect(...strip.coords);
// fill text (inside the clipping region)
c.fillStyle = "hsl(330 80% 50% / 0.8)";
c.fillText(text, ...textRect.bottomLeft.coords);
});
// ⭐️ log
// -------------------------------------------------------
;[
'😃 Have a Good Day!',
].forEach(x => log(x));
0: (?) first recorded
1: (+) refactor, .toString()
2: (+) .inset()
// ------------------
// ⭐️ Rect
// ------------------
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 = Math.min(x, x + width);
this.y = Math.min(y, y + height);
// always positive width/height
this.width = Math.abs(width);
this.height = Math.abs(height);
}
// rect.coords
get coords() {
const {x, y, width, height} = this;
return [x, y, width, height];
}
// convenience properties
get halfWidth() { return this.width / 2 }
get halfHeight() { return this.height / 2 }
// key points/vectors
get origin() { return vec(this.x, this.y) }
get offset() { return vec(this.width, this.height) }
get halfOffset() { return this.offset.times(1/2) }
get center() { return this.origin.add(this.halfOffset) }
get bottomLeft() { return this.center.plus(-this.halfWidth, this.halfHeight) }
}
// convenience factory functions
function rect(corner, offset) { return new Rect(...corner.coords, ...offset.coords) }
// 🔸 Rect.withCenter()
Rect.withCenter = function(
center, // center of rect
offset // offset "vector" to a corner
) {
const corner = center.add(offset);
const offsetToOppositeCorner = offset.times(-2);
return rect(corner, offsetToOppositeCorner);
};
// 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
// .center, .bottomLeft
// -------------------------------------------------
// .halfWidth, .halfHeight, .halfOffset
// -------------------------------------------------
// 🔹 .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);
}
// rect.coords
get coords() {
const {x, y, width, height} = this;
return [x, y, width, height];
}
// .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 }
// .origin, .offset, .halfOffset
get origin() { return vec(this.x, this.y) } // 👔 Vector
get offset() { return vec(this.width, this.height) } // 👔 Vector
get halfOffset() { return this.offset.times(1/2) } // 👔 Vector
// .center, .bottomLeft
get center() { return this.origin.plus(this.halfOffset) } // 👔 Vector
get bottomLeft() {
const {halfWidth: w, halfHeight: h} = this; // 👔 Vector
return this.center.plus(-w, h); // 👔 Vector
}
// 🔹 .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
replit ⟩ various boxes , require -> Vector, Size
// 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
// -------------------------------------------------
// .halfWidth, .halfHeight, .halfOffset
// -------------------------------------------------
// 🔹 .inset()
// -------------------------------------------------
// 🔹 .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, .offset, .halfOffset
get origin() { return vec(this.x, this.y) } // 👔 Vector
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) } // 👔 Vector
// 🔸 .bottomLeft
get bottomLeft() {
const {halfWidth: w, halfHeight: h} = this; // 👔 Vector
return this.center.plus(-w, h); // 👔 Vector
}
// 🔸 .bottomRight
get bottomRight() {
return this.origin.plus(this.offset); // 👔 Vector
}
// ------------------------
// rect
// ------------------------
// 🔹 .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);
}
// ------------------------
// 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
replit:Rect