Third work done
This commit is contained in:
parent
9e4f5cedfa
commit
a8269918a2
3 changed files with 498 additions and 0 deletions
115
w3/app.js
Normal file
115
w3/app.js
Normal 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
65
w3/index.html
Normal 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
318
w3/nanvas.js
Normal 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;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue