"use strict"; /** * @author nuark (github: NuarkNoir, tg: nuark) */ /** * Double PI, yeah */ const DOUBLE_PI = Math.PI * 2; /** * Base class for dynamic objects */ class Dynamic { /** @type {Number} */ x; /** @type {Number} */ y; constructor(x, y) { this.x = x; this.y = y; } /** * Updates some data * @param {Nanvas} nanvas */ update(nanvas) { throw new Error("[Dynamic] Not implemented yet!"); } /** * Draws dynamic and all * @param {Nanvas} nanvas */ draw(nanvas) { throw new Error("[Dynamic] Not implemented yet!"); } } /** * Particle class */ class Particle extends Dynamic { /** @type {Number} */ id; /** @type {Number} */ x; /** @type {Number} */ y; /** @type {Boolean} */ alive = false; /** @type {Number} */ aliveFor = 0.0; } /** * Particle emitter class */ class ParticleEmmiter extends Dynamic { /** @type {Number} */ emitterSize = 20; /** @type {Number} */ poolSize = 10; /** @type {Number} */ particleLifeTime = 1.0 * 1000; /** @type {Number} */ spawnRate = 200.0; /** @type {Number} */ timeSinceLastSpawn = 0.0; /** @type {Array.} */ pool = new Array(this.poolSize); /** * Constructs object * @param {Number} x position on x * @param {Number} y Position on y * @param {Number} dx Speed over x * @param {Number} dy Speed over y */ constructor(x, y, dx, dy) { super(x, y); this.dx = dx; this.dy = dy; this.lastFrameTime = Date.now(); for (let i = 0; i < this.poolSize; i++) { this.pool[i] = new Particle(this.x, this.y); this.pool[i].id = i; } } /** * Updates particles data * @param {Nanvas} nanvas */ update(nanvas) { const now = Date.now(); const dt = now - this.lastFrameTime; this.lastFrameTime = now; this.timeSinceLastSpawn += dt; if (this.timeSinceLastSpawn >= this.spawnRate) { this.timeSinceLastSpawn = 0.0; for (let i = 0; i < this.poolSize; i++) { if (!this.pool[i].alive) { this.pool[i].x = this.x + this.emitterSize/2; this.pool[i].y = this.y + this.emitterSize/2; this.pool[i].alive = true; this.pool[i].aliveFor = 0.0; console.log(`Spawned ${this.pool[i].id}`); break; } } } for (let i = 0; i < this.poolSize; i++) { if (this.pool[i].alive) { this.pool[i].x += this.dx*dt; this.pool[i].y += this.dy*dt; this.pool[i].aliveFor += dt; if (this.pool[i].aliveFor >= this.particleLifeTime) { this.pool[i].alive = false; console.log(`Killed ${this.pool[i].id}`); } } } } /** * Draws emitter and particles * @param {Nanvas} nanvas */ draw(nanvas) { nanvas.square(this.x, this.y, this.emitterSize); for (let i = 0; i < this.poolSize; i++) { if (this.pool[i].alive) { nanvas.circle(this.pool[i].x, this.pool[i].y, 2); } } } } /** * Class for working with canvas */ class Nanvas { /** @type {CanvasRenderingContext2D} */ ctx = null; /** @type {Number} */ width; /** @type {Number} */ height; /** @type {Array.>} */ commands = []; /** @type {Array.} */ dynamics = []; /** * Pushes drawing style on stack */ push() { this.ctx.save(); } /** * Pops drawing style from stack */ pop() { this.ctx.restore(); } /** * Fill background with colour * @param {String} color Fill color */ background(color) { this.push(); this.ctx.fillStyle = color; this.ctx.fillRect(0, 0, this.width, this.height); this.pop(); } /** * Clears canvas background */ clear() { this.background("white"); } /** * Draws nifty grid */ grid() { this.push(); this.ctx.beginPath(); for (var x = 0; x < this.width; x += 10) { this.ctx.moveTo(x, 0); this.ctx.lineTo(x, this.height); } for (var y = 0; y < this.height; y += 10) { this.ctx.moveTo(0, y); this.ctx.lineTo(this.width, y); } this.ctx.strokeStyle = "#eee"; this.ctx.stroke(); this.pop(); } /** * Pushes commands into canvas * @param {Array.>} commands Commands array */ pushCommands(commands) { this.commands = commands; } /** * Clears canvas commands */ clearCommands() { this.commands = []; } /** * Draws static objects (commands) */ drawStatic() { this.commands.forEach(command => { let [cmd, ...args] = command; if (typeof (this[cmd]) !== typeof (() => { })) { throw new Error(`No such function: ${cmd}`); } this[cmd](...args); }); } /** * Draws circle at postion `(x,y)` with radius `r` * @param {Number} x Position on x * @param {Number} y Position on y * @param {Number} r Radius */ circle(x, y, r) { this.ctx.beginPath(); this.ctx.arc(x, y, r, 0, DOUBLE_PI); this.ctx.stroke(); } /** * Draws square at postion `(x,y)` with width `w` * @param {Number} x Position on x * @param {Number} y Position on y * @param {Number} r Width */ square(x, y, w) { this.ctx.beginPath(); this.ctx.rect(x, y, w, w); this.ctx.stroke(); } /** * Draws Ellipse at postion `(x,y)` with radiais as `r1` and `r2` * @param {Number} x Position on x * @param {Number} y Position on y * @param {Number} r1 Radius 1 * @param {Number} r2 Radius 2 */ ellipse(x, y, r1, r2) { this.ctx.beginPath(); this.ctx.ellipse(x, y, r1, r2, 0, 0, DOUBLE_PI); this.ctx.stroke(); } /** * Draws bezier curve with following parameters * @param {Number} h1x Position of first handle on x * @param {Number} h1y Position of first handle on y * @param {Number} h2x Position of second handle on x * @param {Number} h2y Position of second handle on y * @param {Number} x Position of end point on x * @param {Number} y Position of end point on y */ bezier(h1x, h1y, h2x, h2y, x, y) { this.ctx.beginPath(); this.ctx.bezierCurveTo(h1x, h1y, h2x, h2y, x, y); this.ctx.stroke(); } /** * Add dynamic object to canvas * @param {Dynamic} dynamic Dynamic object */ addDynamic(dynamic) { this.dynamics.push(dynamic); } /** * Updates and draws dynamics */ updateDynamics() { this.dynamics.forEach(dyn => dyn.update(this)); this.dynamics.forEach(dyn => dyn.draw(this)); } /** * Mounts Nanvas on existing canvas element and fixes some things * @param {HTMLElement} canvasElement * @returns {Nanvas} */ static mount(canvasElement, width = null, height = null) { const n = new Nanvas(); n.ctx = canvasElement.getContext("2d"); // fixing blurrynessssssssssssssss n.ctx.translate(0.5, 0.5); n.width = width === null ? canvasElement.width : width; n.height = height === null ? canvasElement.height : height; return n; } }