@raducal/isomer
Version:
A simple isometric graphics library for HTML5 canvas
501 lines (500 loc) • 14.3 kB
JavaScript
var z = Object.defineProperty;
var P = (x, t, s) => t in x ? z(x, t, { enumerable: !0, configurable: !0, writable: !0, value: s }) : x[t] = s;
var r = (x, t, s) => (P(x, typeof t != "symbol" ? t + "" : t, s), s);
class m {
constructor(t, s) {
r(this, "elem");
r(this, "ctx");
r(this, "width");
r(this, "height");
r(this, "draw", (t, s, e) => {
if (e) {
const a = this.calculateBoundingBox(t);
this.ctx.lineWidth = Math.min(a.width, a.height) * 0.2, this.ctx.drawImage(
e,
a.x,
a.y,
a.width,
a.height
);
} else
this.ctx.fillStyle = s.toHex(), this.ctx.fill();
this.ctx.stroke(), this.ctx.restore();
});
this.elem = t;
const e = this.elem.getContext("2d", {
willReadFrequently: (s == null ? void 0 : s.willReadFrequently) ?? !1,
alpha: (s == null ? void 0 : s.alpha) ?? !0
});
if (!e)
throw new Error("Context is null");
this.ctx = e;
const a = (s == null ? void 0 : s.dpr) || 1;
this.width = this.elem.width * a, this.height = this.elem.height * a;
}
clear() {
this.ctx.clearRect(0, 0, this.width, this.height);
}
path(t, s, e) {
this.ctx.lineWidth = 1, this.ctx.beginPath(), this.ctx.moveTo(t[0].x, t[0].y);
for (let a = 1; a < t.length; a++)
this.ctx.lineTo(t[a].x, t[a].y);
this.ctx.closePath(), this.ctx.save(), this.ctx.globalAlpha = s.a, this.ctx.strokeStyle = s.toHex(), this.ctx.clip(), this.draw(t, s, e);
}
calculateBoundingBox(t) {
const s = t.map((o) => o.x), e = t.map((o) => o.y), a = Math.min(...s), h = Math.min(...e), n = Math.max(...s), c = Math.max(...e);
return {
x: a,
y: h,
width: n - a,
height: c - h
};
}
}
class y {
constructor(t, s, e, a = 255) {
r(this, "r");
r(this, "g");
r(this, "b");
r(this, "a");
r(this, "h");
r(this, "s");
r(this, "l");
this.init(t, s, e, a);
}
update(t, s, e, a = 255) {
this.init(t, s, e, a);
}
init(t, s, e, a = 255) {
this.r = Math.round(t), this.g = Math.round(s), this.b = Math.round(e), this.a = Math.round(a);
}
duplicate() {
return new y(this.r, this.g, this.b, this.a);
}
toHex() {
let t = (Math.floor(this.r) * 256 * 256 + Math.floor(this.g) * 256 + Math.floor(this.b)).toString(16);
return t.length < 6 && (t = new Array(6 - t.length + 1).join("0") + t), "#" + t;
}
lighten(t, s = new y(255, 255, 255)) {
this.r = s.r / 255 * this.r, this.g = s.g / 255 * this.g, this.b = s.b / 255 * this.b, this.a = s.a / 255 * this.a;
const e = this.calculateHsl();
this.h = e.h, this.s = e.s, this.l = e.l, this.l = Math.min(this.l + t, 1);
const a = this.calculateRgb();
return this.r = a.r, this.g = a.g, this.b = a.b, this;
}
calculateHsl() {
const t = this.r / 255, s = this.g / 255, e = this.b / 255, a = Math.max(t, s, e), h = Math.min(t, s, e);
let n = 0, c, o = (a + h) / 2;
if (a === h)
n = c = 0;
else {
const d = a - h;
switch (c = o > 0.5 ? d / (2 - a - h) : d / (a + h), a) {
case t:
n = (s - e) / d + (s < e ? 6 : 0);
break;
case s:
n = (e - t) / d + 2;
break;
case e:
n = (t - s) / d + 4;
break;
}
n /= 6;
}
return {
h: n,
s: c,
l: o
};
}
calculateRgb() {
let t, s, e;
const a = this.h, h = this.s, n = this.l;
if (h === 0)
t = s = e = n;
else {
const c = n < 0.5 ? n * (1 + h) : n + h - n * h, o = 2 * n - c;
t = this.hue2rgb(o, c, a + 1 / 3), s = this.hue2rgb(o, c, a), e = this.hue2rgb(o, c, a - 1 / 3);
}
return t *= 255, s *= 255, e *= 255, {
r: t,
g: s,
b: e
};
}
hue2rgb(t, s, e) {
return e < 0 && (e += 1), e > 1 && (e -= 1), e < 1 / 6 ? t + (s - t) * 6 * e : e < 1 / 2 ? s : e < 2 / 3 ? t + (s - t) * (2 / 3 - e) * 6 : t;
}
}
const f = class f {
constructor(t = 0, s = 0, e = 0) {
r(this, "x");
r(this, "y");
r(this, "z");
this.init(t, s, e);
}
static FromPoint(t) {
return new f(t.x, t.y, t.z);
}
init(t = 0, s = 0, e = 0) {
this.x = t, this.y = s, this.z = e;
}
update(t = 0, s = 0, e = 0) {
this.init(t, s, e);
}
translate(t = 0, s = 0, e = 0) {
return new f(this.x + t, this.y + s, this.z + e);
}
scale(t, s, e, a) {
let h = this.translate(-t.x, -t.y, -t.z);
return e = typeof e == "number" ? e : s, a = typeof a == "number" ? a : 1, h.x *= s, h.y *= e, h.z *= a, h.translate(t.x, t.y, t.z);
}
rotateX(t, s) {
let e = this.translate(-t.x, -t.y, -t.z), a = e.z * Math.cos(s) - e.y * Math.sin(s), h = e.z * Math.sin(s) + e.y * Math.cos(s);
return e.z = a, e.y = h, e.translate(t.x, t.y, t.z);
}
rotateY(t, s) {
let e = this.translate(-t.x, -t.y, -t.z), a = e.x * Math.cos(s) - e.z * Math.sin(s), h = e.x * Math.sin(s) + e.z * Math.cos(s);
return e.x = a, e.z = h, e.translate(t.x, t.y, t.z);
}
rotateZ(t, s) {
let e = this.translate(-t.x, -t.y, -t.z), a = e.x * Math.cos(s) - e.y * Math.sin(s), h = e.x * Math.sin(s) + e.y * Math.cos(s);
return e.x = a, e.y = h, e.translate(t.x, t.y, t.z);
}
depth() {
return this.x + this.y - 2 * this.z;
}
duplicate() {
return f.FromPoint(this);
}
static distance(t, s) {
let e = s.x - t.x, a = s.y - t.y, h = s.z - t.z;
return Math.sqrt(e * e + a * a + h * h);
}
};
r(f, "ORIGIN", new f(0, 0, 0));
let i = f;
class l {
constructor(t) {
r(this, "points");
this.points = t.map((s) => s.duplicate());
}
getPoints() {
return this.points;
}
duplicate() {
return new l(this.points);
}
push(t) {
this.points.push(t);
}
reverse() {
const t = this.points.slice().reverse();
return new l(t);
}
translate(t = 0, s = 0, e = 0) {
const a = this.points.map((h) => h.translate(t, s, e));
return new l(a);
}
rotateX(t, s) {
const e = this.points.map((a) => a.rotateX(t, s));
return new l(e);
}
rotateY(t, s) {
const e = this.points.map((a) => a.rotateY(t, s));
return new l(e);
}
rotateZ(t, s) {
const e = this.points.map((a) => a.rotateZ(t, s));
return new l(e);
}
scale(t, s, e, a) {
const h = this.points.map(
(n) => n.scale(t, s, e, a)
);
return new l(h);
}
depth() {
return this.points.reduce((t, s) => t + s.depth(), 0) / (this.points.length || 1);
}
static Rectangle(t, s = 1, e = 1) {
return new l([
t,
new i(t.x + s, t.y, t.z),
new i(t.x + s, t.y + e, t.z),
new i(t.x, t.y + e, t.z)
]);
}
static Circle(t, s, e = 20) {
const a = [];
for (let h = 0; h < e; h++)
a.push(
new i(
t.x + s * Math.cos(h * 2 * Math.PI / e),
t.y + s * Math.sin(h * 2 * Math.PI / e),
t.z
)
);
return new l(a);
}
static Star(t, s, e, a) {
const h = [];
for (let n = 0; n < a * 2; n++) {
const c = n % 2 === 0 ? s : e;
h.push(
new i(
t.x + c * Math.cos(n * Math.PI / a),
t.y + c * Math.sin(n * Math.PI / a),
t.z
)
);
}
return new l(h);
}
}
class u {
constructor(t = []) {
r(this, "paths");
this.paths = t.map((s) => s.duplicate());
}
push(t) {
this.paths.push(t);
}
getPaths() {
return this.paths;
}
translate(t = 0, s = 0, e = 0) {
return new u(this.paths.map((a) => a.translate(t, s, e)));
}
rotateX(t, s) {
return new u(this.paths.map((e) => e.rotateX(t, s)));
}
rotateY(t, s) {
return new u(this.paths.map((e) => e.rotateY(t, s)));
}
rotateZ(t, s) {
return new u(this.paths.map((e) => e.rotateZ(t, s)));
}
scale(t, s, e, a) {
return new u(
this.paths.map((h) => h.scale(t, s, e, a))
);
}
orderedPaths() {
return this.paths.slice().sort((t, s) => s.depth() - t.depth());
}
static extrude(t, s = 1) {
const e = t.translate(0, 0, s), a = new u(), h = e.getPoints(), n = t.getPoints();
a.push(t.reverse()), a.push(e);
for (let c = 0; c < n.length; c++)
a.push(
new l([
h[c],
n[c],
n[(c + 1) % n.length],
h[(c + 1) % h.length]
])
);
return a;
}
static Prism(t, s = 1, e = 1, a = 1) {
const h = new u(), n = new l([
t,
new i(t.x + s, t.y, t.z),
new i(t.x + s, t.y, t.z + a),
new i(t.x, t.y, t.z + a)
]);
h.push(n), h.push(n.reverse().translate(0, e, 0));
const c = new l([
t,
new i(t.x, t.y, t.z + a),
new i(t.x, t.y + e, t.z + a),
new i(t.x, t.y + e, t.z)
]);
h.push(c), h.push(c.reverse().translate(s, 0, 0));
const o = new l([
t,
new i(t.x + s, t.y, t.z),
new i(t.x + s, t.y + e, t.z),
new i(t.x, t.y + e, t.z)
]);
return h.push(o.reverse()), h.push(o.translate(0, 0, a)), h;
}
static Pyramid(t, s = 1, e = 1, a = 1) {
const h = new u(), n = new l([
t,
new i(t.x + s, t.y, t.z),
new i(t.x + s / 2, t.y + e / 2, t.z + a)
]);
h.push(n), h.push(n.rotateZ(t.translate(s / 2, e / 2), Math.PI));
const c = new l([
t,
new i(t.x + s / 2, t.y + e / 2, t.z + a),
new i(t.x, t.y + e, t.z)
]);
return h.push(c), h.push(c.rotateZ(t.translate(s / 2, e / 2), Math.PI)), h;
}
static Cylinder(t, s = 1, e, a) {
const h = l.Circle(t, s, e);
return u.extrude(h, a);
}
}
class w {
constructor(t = 0, s = 0, e = 0) {
r(this, "i");
r(this, "j");
r(this, "k");
this.i = t, this.j = s, this.k = e;
}
static fromTwoPoints(t, s) {
return new w(s.x - t.x, s.y - t.y, s.z - t.z);
}
static crossProduct(t, s) {
const e = t.j * s.k - s.j * t.k, a = -1 * (t.i * s.k - s.i * t.k), h = t.i * s.j - s.i * t.j;
return new w(e, a, h);
}
static dotProduct(t, s) {
return t.i * s.i + t.j * s.j + t.k * s.k;
}
magnitude() {
return Math.sqrt(this.i * this.i + this.j * this.j + this.k * this.k);
}
normalize() {
const t = this.magnitude();
return t === 0 ? new w(0, 0, 0) : new w(
this.i / t,
this.j / t,
this.k / t
);
}
}
class M {
constructor(t, s) {
r(this, "drawables");
r(this, "paths");
r(this, "scope");
this.scope = typeof WorkerGlobalScope < "u" && self instanceof WorkerGlobalScope ? "worker" : "main", this.drawables = [], this.paths = [], t && this.updatePaths(t), s && this.updateSources(s);
}
updatePaths(t) {
this.paths = t instanceof u ? t.orderedPaths() : t;
}
updateSources(t) {
this.drawables = [], this.init(t);
}
init(t) {
const s = Array.isArray(t) ? t : [t];
for (const e of s) {
if (this.scope === "worker") {
if (![ImageBitmap, OffscreenCanvas].some(
(o) => e instanceof o
))
continue;
this.drawables.push(e);
continue;
}
[
ImageBitmap,
HTMLImageElement,
HTMLCanvasElement
].some(
(n) => e instanceof n
) && this.drawables.push(e);
}
if (this.drawables.length === 0)
throw new Error(
"No sources could be loaded. Please ensure the correct source types are being passed."
);
}
getPaths() {
return this.paths;
}
getDrawables() {
return this.drawables;
}
}
const k = new y(120, 120, 120);
class p {
constructor(t, s = {}) {
r(this, "canvas");
r(this, "angle");
r(this, "scale");
r(this, "originX");
r(this, "originY");
r(this, "lightPosition");
r(this, "lightAngle");
r(this, "colorDifference");
r(this, "lightColor");
r(this, "transformation");
this.canvas = new m(t, s == null ? void 0 : s.canvas), this.init(s);
}
init(t) {
this.angle = Math.PI / 6, this.scale = t.scale || 70, this.transformation = [
[this.scale * Math.cos(this.angle), this.scale * Math.sin(this.angle)],
[
this.scale * Math.cos(Math.PI - this.angle),
this.scale * Math.sin(Math.PI - this.angle)
]
], this.originX = t.originX || this.canvas.width / 2, this.originY = t.originY || this.canvas.height * 0.9, this.lightPosition = t.lightPosition || new w(2, -1, 3), this.lightAngle = this.lightPosition.normalize(), this.colorDifference = 0.2, this.lightColor = t.lightColor || new y(255, 255, 255);
}
setLightPosition(t, s, e) {
this.lightPosition = new w(t, s, e), this.lightAngle = this.lightPosition.normalize();
}
translatePoint(t) {
const s = new i(
t.x * this.transformation[0][0],
t.x * this.transformation[0][1]
), e = new i(
t.y * this.transformation[1][0],
t.y * this.transformation[1][1]
), a = this.originX + s.x + e.x, h = this.originY - s.y - e.y - t.z * this.scale;
return new i(a, h);
}
add(t, s) {
switch (!0) {
case Array.isArray(t): {
t.forEach((e) => this.add(e, s));
break;
}
case t instanceof l: {
this.addPath(t, s);
break;
}
case t instanceof u: {
t.orderedPaths().forEach((a) => this.addPath(a, s));
break;
}
case t instanceof M: {
const e = t.getPaths(), a = t.getDrawables();
if (a.length > 1 && a.length !== e.length)
throw new Error(
"You must pass in only one image of define an image for each path"
);
e.forEach((h, n) => {
let c = a[0];
a[n] && (c = a[n]), this.addPath(h, s, c);
});
break;
}
default:
throw new Error(
".add could not be executed, please ensure passed parameters are correct"
);
}
}
addPath(t, s = k, e) {
const a = t.getPoints(), h = w.fromTwoPoints(a[1], a[0]), n = w.fromTwoPoints(a[2], a[1]), c = w.crossProduct(h, n).normalize(), o = w.dotProduct(c, this.lightAngle), d = s.lighten(
o * this.colorDifference,
this.lightColor
);
this.canvas.path(
a.map((b) => this.translatePoint(b)),
d,
e
);
}
}
r(p, "Canvas", m), r(p, "Textured", M), r(p, "Color", y), r(p, "Path", l), r(p, "Point", i), r(p, "Shape", u), r(p, "Vector", w);
export {
p as Isomer
};