UNPKG

@raducal/isomer

Version:

A simple isometric graphics library for HTML5 canvas

501 lines (500 loc) 14.3 kB
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 };