318 lines
No EOL
7.6 KiB
JavaScript
318 lines
No EOL
7.6 KiB
JavaScript
"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;
|
|
}
|
|
} |