Third work done

This commit is contained in:
Andrew 2022-02-14 13:22:41 +07:00
parent 9e4f5cedfa
commit a8269918a2
3 changed files with 498 additions and 0 deletions

115
w3/app.js Normal file
View file

@ -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.<Number, Number>} 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);

65
w3/index.html Normal file
View file

@ -0,0 +1,65 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
* {
margin: 0;
padding: 0;
}
body {
background-color: cornsilk;
margin: 2rem;
}
main#main {
display: flex;
flex-direction: row;
gap: 2rem;
height: 100%;
}
#commandArea {
flex: 20%;
display: flex;
flex-direction: column;
gap: 1rem;
}
#canvasArea {
flex: 80%;
display: flex;
}
#commandArea > #drawCommands,
#canvasArea > canvas {
flex-grow: 1;
}
</style>
</head>
<body>
<main id="main">
<div id="commandArea">
<h4>Draw commands</h4>
<textarea name="drawCommands" id="drawCommands">[
["circle",50,50,10],
["square",150,50,25],
["ellipse",200,200,30,40],
["bezier", 50, 100, 180, 10, 20, 10]
]</textarea>
</div>
<div id="canvasArea">
<canvas id="canvas"></canvas>
</div>
</main>
<br>
<p id="errorHolder"></p>
</body>
<script src="./nanvas.js"></script>
<script src="./app.js"></script>
</html>

318
w3/nanvas.js Normal file
View file

@ -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.<Particle>} */ 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.<Array.<String, ...Number>>} */
commands = [];
/** @type {Array.<Dynamic>} */
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.<Array.<String, ...Number>>} 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;
}
}