From a8269918a23147b2b8a457086c20c111c6735808 Mon Sep 17 00:00:00 2001 From: Andrew nuark G Date: Mon, 14 Feb 2022 13:22:41 +0700 Subject: [PATCH] Third work done --- w3/app.js | 115 ++++++++++++++++++ w3/index.html | 65 +++++++++++ w3/nanvas.js | 318 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 498 insertions(+) create mode 100644 w3/app.js create mode 100644 w3/index.html create mode 100644 w3/nanvas.js diff --git a/w3/app.js b/w3/app.js new file mode 100644 index 0000000..14824e5 --- /dev/null +++ b/w3/app.js @@ -0,0 +1,115 @@ +"use strict"; + +/** + * @author nuark (github: NuarkNoir, tg: nuark) + */ + +/** @type {HTMLElement} */ +let drawCommandsElement = null; + +/** @type {HTMLElement} */ +let errorHolderElement = null; + +/** @type {HTMLElement} */ +let canvasElement = null; + +/** @type {Nanvas} */ +let nanvas = null; + +/** + * Drawing loop + */ +const drawingLoop = function () { + nanvas.clear(); + nanvas.grid(); + try { + nanvas.drawStatic(); + nanvas.updateDynamics(); + } catch (e) { + console.error(e.stack); + errorHolderElement.textContent = `[Error] ${e.message}`; + } + + requestAnimationFrame(drawingLoop); +} + +/** + * Checks for correct input in commands textarea + * @returns {Error} + */ +const processInput = function () { + try { + const commands = JSON.parse(drawCommandsElement.value); + nanvas.pushCommands(commands); + } catch (e) { return e; } + return null; +} + +/** + * Handles command input changes + */ +const onCommandsEdited = function () { + errorHolderElement.textContent = ""; + const errored = processInput(); + if (errored !== null) { + errorHolderElement.textContent = `[Error] ${errored.message}`; + nanvas.clearCommands(); + } +} + +/** + * Returns position of cursor on canvas + * @param {HTMLElement} canvas + * @param {Event} event + * @returns {Array.} Position `x` and `y` in array + */ +const getCursorPosition = function (canvas, event) { + const rect = canvas.getBoundingClientRect(); + const x = event.clientX - rect.left; + const y = event.clientY - rect.top; + return [x, y]; +} + +/** + * Spawns dynamic object on canvas + * @param {Number} x Position of dynamic on x + * @param {Number} y Position of dynamic on y + */ +const spawnDynamic = function (x, y) { + const emitter = new ParticleEmmiter( + ~~x, ~~y, + -1 + Math.random() * 2, -1 + Math.random() * 2 + ); + nanvas.addDynamic(emitter); +} + +/** + * Init function + */ +const init = function () { + drawCommandsElement = document.getElementById("drawCommands"); + errorHolderElement = document.getElementById("errorHolder"); + canvasElement = document.getElementById("canvas"); + + drawCommandsElement.addEventListener("input", onCommandsEdited); + + canvasElement.addEventListener("mousedown", function (e) { + const [x, y] = getCursorPosition(canvasElement, e) + spawnDynamic(x, y); + }); + canvasElement.height = canvasElement.clientHeight; + canvasElement.width = canvasElement.clientWidth; + nanvas = Nanvas.mount(canvasElement); + nanvas.pushCommands([ + ["circle", 50, 50, 10], + ["square", 150, 50, 25], + ["ellipse", 200, 200, 30, 40], + ["bezier", 50, 100, 180, 10, 20, 10] + ]); + requestAnimationFrame(drawingLoop); +} + +/** + * Subscribbes `init()` for DOMContentLoaded + */ +document.addEventListener("DOMContentLoaded", init); diff --git a/w3/index.html b/w3/index.html new file mode 100644 index 0000000..bef80d5 --- /dev/null +++ b/w3/index.html @@ -0,0 +1,65 @@ + + + + + + + Document + + + + +
+
+

Draw commands

+ +
+
+ +
+
+
+

+ + + + \ No newline at end of file diff --git a/w3/nanvas.js b/w3/nanvas.js new file mode 100644 index 0000000..8142e91 --- /dev/null +++ b/w3/nanvas.js @@ -0,0 +1,318 @@ +"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; + } +} \ No newline at end of file