JS canvas animation, my effect is accelerating and accumulating, but the speed of the effect is in the console same? - javascript

I tried to create a canvas effect with fireworks, but the more you click, the faster it gets and it seems to accumulate on itself. When I listed the speed it was similar and did not correspond to what was happening there. I also tried to cancel the draw if it got out of the canvas but it didn´t help.
Here is link https://dybcmwd8icxxdxiym4xkaw-on.drv.tw/canvasTest.html
var fireAr = [];
var expAr = [];
function Firework(x, y, maxY, maxX, cn, s, w, en) {
this.x = x;
this.y = y;
this.maxY = maxY;
this.maxX = maxX;
this.cn = cn;
this.s = s;
this.w = w;
this.en = en;
this.i = 0;
this.explosion = function() {
for (; this.i < this.en; this.i++) {
var ey = this.maxY;
var ex = this.maxX;
var ecn = Math.floor(Math.random() * color.length);
var esX = (Math.random() - 0.5) * 3;
var esY = (Math.random() - 0.5) * 3;
var ew = Math.random() * 10;
var t = true;
expAr.push(new Exp(ew, esX, esY, ex, ey, ecn, t));
}
for (var e = 0; e < expAr.length; e++) {
expAr[e].draw();
}
}
this.draw = function() {
if (this.y < this.maxY) {
this.explosion();
} else {
this.track();
this.y -= this.s;
}
}
}
function Exp(ew, esX, esY, ex, ey, ecn, t) {
this.ew = ew;
this.esX = esX;
this.esY = esY;
this.ex = ex;
this.ey = ey;
this.ecn = ecn;
this.t = t;
this.draw = function() {
if (this.t == true) {
c.beginPath();
c.shadowBlur = 20;
c.shadowColor = color[this.ecn];
c.rect(this.ex, this.ey, this.ew, this.ew);
c.fillStyle = color[this.ecn];
c.fill();
c.closePath();
this.ex += this.esX;
this.ey += this.esY;
}
}
}
window.addEventListener('click', function(event) {
var x = event.clientX;
var y = canvas.height;
mouse.clickX = event.clientX;
mouse.clickY = event.clientY;
var maxY = event.clientY;
var maxX = event.clientX;
var cn = Math.floor(Math.random() * color.length);
var s = Math.random() * 5 + 5;
var w = Math.random() * 20 + 2;
var en = Math.random() * 50 + 5;
fireAr.push(new Firework(x, y, maxY, maxX, cn, s, w, en));
});
function ani() {
requestAnimationFrame(ani);
c.clearRect(0, 0, canvas.width, canvas.height);
for (var i = 0; i < fireAr.length; i++) {
fireAr[i].draw();
}
}
ani();
I deleted some unnecessary parts in my opinion but if I'm wrong and I missed something I'll try to fix it

Here are a few simple ways you can improve performance:
Commenting out shadowBlur gives a noticeable boost. If you need shadows, see this answer which illustrates pre-rendering.
Try using fillRect and ctx.rotate() instead of drawing a path. Saving/rotating/restoring the canvas might be prohibitive, so you could use non-rotated rectangles.
Consider using a smaller canvas which is quicker to repaint than one that may fill the entire window.
Another issue is more subtle: Fireworks and Exps are being created (making objects is expensive!) and pushed onto arrays. But these arrays are never trimmed and objects are never reused after they've left the visible canvas. Eventually, the rendering loop gets bogged down by all of the computation for updating and rendering every object in the fireAr and expAr arrays.
A naive solution is to check for objects exiting the canvas and splice them from the expAr. Here's pseudocode:
for (let i = expAr.length - 1; i >= 0; i--) {
if (!inBounds(expAr[i], canvas)) {
expAr.splice(i, 1);
}
}
Iterate backwards since this mutates the array's length. inBounds is a function that checks an Exp object's x and y properties along with its size and the canvas width and height to determine if it has passed an edge. More pseudocode:
function inBounds(obj, canvas) {
return obj.x >= 0 && obj.x <= canvas.width &&
obj.y >= 0 && obj.y <= canvas.height;
}
This check isn't exactly correct since the rectangles are rotated. You could check each corner of the rectangle with a pointInRect function to ensure that at least one is inside the canvas.
Fireworks can be spliced out when they "explode".
splice is an expensive function that walks up to the entire array to shift items forward to fill in the vacated element. Splicing multiple items in a loop gives quadratic performance. This can be made linear by putting surviving fireworks in a new list and replacing the previous generation on each frame. Dead firework objects can be saved in a pool for reuse.
Beyond that, I strongly recommend using clear variable names.
this.cn = cn;
this.s = s;
this.w = w;
this.en = en;
this.i = 0;
These names have little or no meaning to an outside reader and are unlikely to mean much to you if you take a couple months away from the code. Use full words like "size", "width", etc.
Another side point is that it's a good idea to debounce your window resize listener.
Here's a quick proof of concept that illustrates the impact of shadowBlur and pruning dead elements.
const rnd = n => ~~(Math.random() * n);
const mouse = {pressed: false, x: 0, y: 0};
let fireworks = [];
let shouldSplice = false;
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
document.body.appendChild(canvas);
document.body.style.margin = 0;
canvas.style.background = "#111";
canvas.width = document.body.scrollWidth;
canvas.height = document.body.clientHeight;
ctx.shadowBlur = 0;
const fireworksAmt = document.querySelector("#fireworks-amt");
document.querySelector("input[type=range]").addEventListener("change", e => {
ctx.shadowBlur = e.target.value;
document.querySelector("#shadow-amt").textContent = ctx.shadowBlur;
});
document.querySelector("input[type=checkbox]").addEventListener("change", e => {
shouldSplice = !shouldSplice;
});
const createFireworks = (x, y) => {
const color = `hsl(${rnd(360)}, 100%, 60%)`;
return Array(rnd(20) + 1).fill().map(_ => ({
x: x,
y: y,
vx: Math.random() * 6 - 3,
vy: Math.random() * 6 - 3,
size: rnd(4) + 2,
color: color
}));
}
(function render() {
if (mouse.pressed) {
fireworks.push(...createFireworks(mouse.x, mouse.y));
}
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (const e of fireworks) {
e.x += e.vx;
e.y += e.vy;
e.vy += 0.03;
ctx.beginPath();
ctx.fillStyle = ctx.shadowColor = e.color;
ctx.arc(e.x, e.y, e.size, 0, Math.PI * 2);
ctx.fill();
if (shouldSplice) {
e.size -= 0.03;
if (e.size < 1) {
e.dead = true;
}
}
}
fireworks = fireworks.filter(e => !e.dead);
fireworksAmt.textContent = "fireworks: " + fireworks.length;
requestAnimationFrame(render);
})();
let debounce;
addEventListener("resize", e => {
clearTimeout(debounce);
debounce = setTimeout(() => {
canvas.width = document.body.scrollWidth;
canvas.height = document.body.clientHeight;
}, 100);
});
canvas.addEventListener("mousedown", e => {
mouse.pressed = true;
});
canvas.addEventListener("mouseup", e => {
mouse.pressed = false;
});
canvas.addEventListener("mousemove", e => {
mouse.x = e.offsetX;
mouse.y = e.offsetY;
});
* {
font-family: monospace;
user-select: none;
}
div > span, body > div {padding: 0.5em;}
canvas {display: block;}
<div>
<div id="fireworks-amt">fireworks: 0</div>
<div>
<label>splice? </label>
<input type="checkbox">
</div>
<div>
<label>shadowBlur (<span id="shadow-amt">0</span>): </label>
<input type="range" value=0>
</div>
</div>

Related

How can I reverse the direction of this square after it reaches a certain value?

I'm trying to create an idle animation where the red rectangle moves back and forth slightly in a loop. For some reason once it reaches the specified threshhold instead of proceeding to move in the opposite direction, it just stops.
What did I do wrong?
<canvas id="myCanvas" width="1500" height="500" style="border:1px solid #c3c3c3;">
Your browser does not support the canvas element.
</canvas>
<script>
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
// Spaceship structure
var shipWidth = 250;
var shipHeight = 100;
// Canvas parameters
var cWidth = canvas.width;
var cHeight = canvas.height;
// Positioning variables
var centerWidthPosition = (cWidth / 2) - (shipWidth / 2);
var centerHeightPosition = (cHeight / 2) - (shipHeight / 2);
var requestAnimationFrame = window.requestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.msRequestAnimationFrame;
function drawShip(){
ctx.clearRect(0, 0, cWidth, cHeight);
ctx.fillStyle = "#FF0000";
ctx.fillRect(centerWidthPosition,centerHeightPosition,shipWidth,shipHeight);
centerWidthPosition--;
if (centerWidthPosition < 400){
++centerWidthPosition;
}
requestAnimationFrame(drawShip);
}
drawShip();
</script>
#TheAmberlamps explained why it's doing that. Here I offer you a solution to achieve what I believe you are trying to do.
Use a velocity variable that changes magnitude. X position always increases by velocity value. Velocity changes directions at screen edges.
// use a velocity variable
var xspeed = 1;
// always increase by velocity
centerWidthPosition += xspeed;
// screen edges are 0 and 400 in this example
if (centerWidthPosition > 400 || centerWidthPosition < 0){
xspeed *= -1; // change velocity direction
}
I added another condition in your if that causes the object to bounce back and forth. Remove the selection after || if you don't want it doing that.
Your function is caught in a loop; once centerWidthPosition reaches 399 your conditional makes it increment back up to 400, and then it decrements back to 399.
here is another one as a brain teaser - how would go by making this animation bounce in the loop - basically turn text into particles and then reverse back to text and reverse back to particles and back to text and so on and on and on infinitely:
var random = Math.random;
window.onresize = function () {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
};
window.onresize();
var ctx = canvas.getContext('2d');
ctx.font = 'bold 50px "somefont"';
ctx.textBaseline = 'center';
ctx.fillStyle = 'rgba(255,255,255,1)';
var _particles = [];
var particlesLength = 0;
var currentText = "SOMETEXT";
var createParticle = function createParticle(x, y) {_particles.push(new Particle(x, y));};
var checkAlpha = function checkAlpha(pixels, i) {return pixels[i * 4 + 3] > 0;};
var createParticles = function createParticles() {
var textSize = ctx.measureText(currentText);
ctx.fillText(currentText,Math.round((canvas.width / 2) - (textSize.width / 2)),Math.round(canvas.height / 2));
var imageData = ctx.getImageData(1, 1, canvas.width, canvas.height);
var pixels = imageData.data;
var dataLength = imageData.width * imageData.height;
for (var i = 0; i < dataLength; i++) {
var currentRow = Math.floor(i / imageData.width);
var currentColumn = i - Math.floor(i / imageData.height);
if (currentRow % 2 || currentColumn % 2) continue;
if (checkAlpha(pixels, i)) {
var cy = ~~(i / imageData.width);
var cx = ~~(i - (cy * imageData.width));
createParticle(cx, cy);
}}
particlesLength = _particles.length;
};
var Point = function Point(x, y) {
this.set(x, y);
};
Point.prototype = {
set: function (x, y) {
x = x || 0;
y = y || x || 0;
this._sX = x;
this._sY = y;
this.reset();
},
add: function (point) {
this.x += point.x;
this.y += point.y;
},
multiply: function (point) {
this.x *= point.x;
this.y *= point.y;
},
reset: function () {
this.x = this._sX;
this.y = this._sY;
return this;
},
};
var FRICT = new Point(0.98);//set to 0 if no flying needed
var Particle = function Particle(x, y) {
this.startPos = new Point(x, y);
this.v = new Point();
this.a = new Point();
this.reset();
};
Particle.prototype = {
reset: function () {
this.x = this.startPos.x;
this.y = this.startPos.y;
this.life = Math.round(random() * 300);
this.isActive = true;
this.v.reset();
this.a.reset();
},
tick: function () {
if (!this.isActive) return;
this.physics();
this.checkLife();
this.draw();
return this.isActive;
},
checkLife: function () {
this.life -= 1;
this.isActive = !(this.life < 1);
},
draw: function () {
ctx.fillRect(this.x, this.y, 1, 1);
},
physics: function () {
if (performance.now()<nextTime) return;
this.a.x = (random() - 0.5) * 0.8;
this.a.y = (random() - 0.5) * 0.8;
this.v.add(this.a);
this.v.multiply(FRICT);
this.x += this.v.x;
this.y += this.v.y;
this.x = Math.round(this.x * 10) / 10;
this.y = Math.round(this.y * 10) / 10;
}
};
var nextTime = performance.now()+3000;
createParticles();
function clearCanvas() {
ctx.fillStyle = 'rgba(0,0,0,1)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
(function clearLoop() {
clearCanvas();
requestAnimationFrame(clearLoop);
})();
(function animLoop(time) {
ctx.fillStyle = 'rgba(255,255,255,1)';
var isAlive = true;
for (var i = 0; i < particlesLength; i++) {
if (_particles[i].tick()) isAlive = true;
}
requestAnimationFrame(animLoop);
})();
function resetParticles() {
for (var i = 0; i < particlesLength; i++) {
_particles[i].reset();
}}

My grid quickly disappears on page load due to incorrect force being applied?

So I'm recreating the warping grid from Geometry Wars in a web page to further test my skills with JavaScript and I've hit another snag. I'm following a tutorial written in C# over on TutsPlus that I used a long time ago to recreate it while learning XNA Framework. The tutorial is straight forward, and most of the code is self-explanatory, but I think my lack of superior education in mathematics is letting me down once again.
I've successfully rendered the grid in a 300x300 canvas with no troubles, and even replicated all of the code in the tutorial, but since they're using the XNA Framework libraries, they have the advantage of not having to write the mathematical functions of the Vector3 type. I've implemented only what I need, but I believe I may have gotten my math incorrect or perhaps the implementation.
The initial grid (above) should look like this until I begin interacting with it, and it does, as long as I disable the Update function of my Grid. I've stepped through the code and the issue seems to be related to my calculation for the magnitude of my vectors. The XNA Framework libraries always called it Length and LengthSquared, but each Google search I performed was returning results for calculating magnitude as:
Now, this is incredibly simple to recreate in code, and my Vector3 class accounts for Magnitude and MagnitudeSquared since the tutorial calls for both. I've compared the results of my magnitude calculation to that of an online calculator and the results were the same:
V = (2, 3, 4)
|V| = 5.385164807134504
To top this off, the URL for this calculator says that I'm calculating the length of the vector. This is what leads me to believe that it may be my implementation here that is causing the whole thing to go crazy. I've included my snippet below, and it is unfortunately a bit long, but I assure you it has been trimmed as much as possible.
class Vector3 {
constructor(x, y, z) {
this.X = x;
this.Y = y;
this.Z = z;
}
Add(val) {
this.X += val.X;
this.Y += val.Y;
this.Z += val.Z;
}
Subtract(val) {
this.X -= val.X;
this.Y -= val.Y;
this.Z -= val.Z;
}
MultiplyByScalar(val) {
let result = new Vector3(0, 0, 0);
result.X = this.X * val;
result.Y = this.Y * val;
result.Z = this.Z * val;
return result;
}
DivideByScalar(val) {
let result = new Vector3(0, 0, 0);
result.X = this.X / val;
result.Y = this.Y / val;
result.Z = this.Z / val;
return result;
}
Magnitude() {
if (this.X == 0 && this.Y == 0 && this.Z == 0)
return 0;
return Math.sqrt(Math.pow(this.X, 2) +
Math.pow(this.Y, 2) +
Math.pow(this.Z, 2));
}
MagnitudeSquared() {
return Math.pow(this.Magnitude(), 2);
}
DistanceFrom(to) {
let x = Math.pow(this.X - to.X, 2);
let y = Math.pow(this.Y - to.Y, 2);
let z = Math.pow(this.Z - to.Z, 2);
return Math.sqrt(x + y + z);
}
}
class PointMass {
Acceleration = new Vector3(0, 0, 0);
Velocity = new Vector3(0, 0, 0);
Damping = 0.95;
constructor(position, inverseMass) {
this.Position = position;
this.InverseMass = inverseMass;
}
IncreaseDamping(factor) {
this.Damping *= factor;
}
ApplyForce(force) {
this.Acceleration.Add(force.MultiplyByScalar(this.InverseMass));
}
Update() {
this.Velocity.Add(this.Acceleration);
this.Position.Add(this.Velocity);
this.Acceleration = new Vector3(0, 0, 0);
if (this.Velocity.MagnitudeSquared() < 0.001 * 0.001)
Velocity = new Vector3(0, 0, 0);
this.Velocity.MultiplyByScalar(this.Damping);
this.Damping = 0.95;
}
}
class Spring {
constructor(startPoint, endPoint, stiffness, damping) {
this.StartPoint = startPoint;
this.EndPoint = endPoint;
this.Stiffness = stiffness;
this.Damping = damping;
this.TargetLength = startPoint.Position.DistanceFrom(endPoint.Position) * 0.95;
}
Update() {
let x = this.StartPoint.Position;
x.Subtract(this.EndPoint.Position);
let magnitude = x.Magnitude();
if (magnitude < this.TargetLength || magnitude == 0)
return;
x = x.DivideByScalar(magnitude).MultiplyByScalar(magnitude - this.TargetLength);
let dv = this.EndPoint.Velocity;
dv.Subtract(this.StartPoint.Velocity);
let force = x.MultiplyByScalar(this.Stiffness)
force.Subtract(dv.MultiplyByScalar(this.Damping));
this.StartPoint.ApplyForce(force);
this.EndPoint.ApplyForce(force);
}
}
class Grid {
Springs = [];
Points = [];
constructor(containerID, spacing) {
this.Container = document.getElementById(containerID);
this.Width = this.Container.width;
this.Height = this.Container.height;
this.ColumnCount = this.Width / spacing + 1;
this.RowCount = this.Height / spacing + 1;
let columns = [];
let fixedColumns = [];
let rows = [];
let fixedRows = [];
let fixedPoints = [];
for (let y = 0; y < this.Height; y += spacing) {
for (let x = 0; x < this.Width; x += spacing) {
columns.push(new PointMass(new Vector3(x, y, 0), 1));
fixedColumns.push(new PointMass(new Vector3(x, y, 0), 0));
}
rows.push(columns);
fixedRows.push(fixedColumns);
columns = [];
fixedColumns = [];
}
this.Points = rows;
for (let y = 0; y < rows.length; y++) {
for (let x = 0; x < rows[y].length; x++) {
if (x == 0 || y == 0 || x == rows.length - 1 || x == rows[y].length - 1)
this.Springs.push(new Spring(fixedRows[x][y], this.Points[x][y], 0.1, 0.1));
else if (x % 3 == 0 && y % 3 == 0)
this.Springs.push(new Spring(fixedRows[x][y], this.Points[x][y], 0.002, 0.002));
const stiffness = 0.28;
const damping = 0.06;
if (x > 0)
this.Springs.push(new Spring(this.Points[x - 1][y], this.Points[x][y], stiffness, damping));
if (y > 0)
this.Springs.push(new Spring(this.Points[x][y - 1], this.Points[x][y], stiffness, damping));
}
}
}
ApplyDirectedForce(force, position, radius) {
this.Points.forEach(function(row) {
row.forEach(function(point) {
if (point.Position.DistanceFrom(position) < Math.pow(radius, 2))
point.ApplyForce(force.MultiplyByScalar(10).DivideByScalar(10 + point.Position.DistanceFrom(position)));
});
});
}
ApplyImplosiveForce(force, position, radius) {
this.Points.forEach(function(point) {
let distance_squared = Math.pow(point.Position.DistanceFrom(position));
if (distance_squared < Math.pow(radius, 2)) {
point.ApplyForce(force.MultiplyByScalar(10).Multiply(position.Subtract(point.Position)).DivideByScalar(100 + distance_squared));
point.IncreaseDamping(0.6);
}
});
}
ApplyExplosiveForce(force, position, radius) {
this.Points.forEach(function(point) {
let distance_squared = Math.pow(point.Position.DistanceFrom(position));
if (distance_squared < Math.pow(radius, 2)) {
point.ApplyForce(force.MultiplyByScalar(100).Multiply(point.Position.Subtract(position)).DivideByScalar(10000 + distance_squared));
point.IncreaseDamping(0.6);
}
});
}
Update() {
this.Springs.forEach(function(spring) {
spring.Update();
});
this.Points.forEach(function(row) {
row.forEach(function(point) {
point.Update();
});
});
}
Draw() {
const context = this.Container.getContext('2d');
context.clearRect(0, 0, this.Width, this.Height);
context.strokeStyle = "#ffffff";
context.fillStyle = "#ffffff";
for (let y = 1; y < this.Points.length; y++) {
for (let x = 1; x < this.Points[y].length; x++) {
let left = new Vector3(0, 0, 0);
let up = new Vector3(0, 0, 0);
if (x > 1) {
left = this.Points[x - 1][y].Position;
context.beginPath();
context.moveTo(left.X, left.Y);
context.lineTo(this.Points[x][y].Position.X, this.Points[x][y].Position.Y);
context.stroke();
}
if (y > 1) {
up = this.Points[x][y - 1].Position;
context.beginPath();
context.moveTo(up.X, up.Y);
context.lineTo(this.Points[x][y].Position.X, this.Points[x][y].Position.Y);
context.stroke();
}
let radius = 3;
if (y % 3 == 1)
radius = 5;
context.beginPath();
context.arc(this.Points[x][y].Position.X, this.Points[x][y].Position.Y, radius, 0, 2 * Math.PI);
context.fill();
}
}
}
}
var grid = new Grid("grid", 40);
setInterval(function() {
grid.Update();
grid.Draw();
}, 5);
var mouseX = 0;
var mouseY = 0;
function updateMouseCoordinates(evt) {
var rect = grid.Container.getBoundingClientRect();
mouseX = evt.clientX - rect.left;
mouseY = evt.clientY - rect.top;
const context = grid.Container.getContext('2d');
context.clearRect(0, 0, this.Width, this.Height);
context.strokeStyle = "#ffffff";
context.fillStyle = "#ff3333";
context.beginPath();
context.arc(mouseX, mouseY, 15, 0, 2 * Math.PI);
context.fill();
grid.ApplyDirectedForce(new Vector3(0, 0, 5000), new Vector3(mouseX, mouseY, 0), 50);
}
html,
body {
margin: 0;
height: 100%;
background: #213;
background: linear-gradient(45deg, #213, #c13);
background: -webkit-linear-gradient(45deg, #213, #c13);
}
.container {
position: relative;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
<div class="container">
<canvas onmousemove="updateMouseCoordinates(event)" id="grid" class="grid" width="300" height="300"></canvas>
</div>
I believe the issue has something to do with the Update method in the Spring and PointMass classes as when I stepped through my code, I kept finding that the PointMass objects seemed to have acceleration when they shouldn't (as in, I haven't interacted with them yet). In all honesty, I think it's the implementation of my custom Vector3 functions in those update functions that are causing the issue but for the life of me, I can't figure out what I've done incorrectly here.
Perhaps I just need to take a break and come back to it, but I'm hoping someone here can help spot an incorrect implementation.
How do I prevent my grid from immediately dissipating due to forces that have not yet been exerted (as in they are just miscalculations)?
My advice is reduce the problem down. Have only a single point, slow the interval down, step through to see what's happening. The mouse doesn't appear to be doing anything. Commenting out the line grid.ApplyDirectedForce(new Vector3(0, 0, 5000), new Vector3(mouseX, mouseY, 0), 50); doesn't change the output. It goes wrong in grid.Update(), for some reason grid.Update() does something even if there's no force applied, maybe that means the spring code has a bug. The bottom right point doesn't move frame one maybe that means something. The debugger is your friend. Add a breakpoint to grid.Update() and see what the code is actually doing. I know this isn't a direct answer but I hope this guides you in the right direction.
I also want to point out that usually the whole point of Magnitude Squared is so that you can compare vectors or distances without having to do a square root operation. That is, in your Magnitude function you do a Square root operation and then in your Magnitude Squared function you square it. This is is the same as simply going x^2 + y^2 + z^2
frame 1:
frame 2:

Click listener incorrect data after resizing window with canvas in angular 6

I am using a canvas with clickable elements that was added using for loop, I added a resizing event that redrawing the canvas after user window was resized, When the window is loading for the first time the canvas and click listeners works great, My problem starts after the window resizing, I getting wrong click coordinates and bad behavior, It looks the click event have sort of a backlog for all the times that the screen was resized
Here is the full code on stackblitz
The resize function
#HostListener('window:resize', ['$event'])
onResize(event) {
this.innerWidth = window.innerWidth;
this.innerHeight = window.innerHeight;
this.canvas.width = this.innerWidth;
this.canvas.height = this.innerHeight
this.cleardraw()
this.draw()
}
cleardraw(){
var ctx = this.canvas.getContext('2d');
ctx.clearRect(0, 0, this.innerWidth, this.innerHeight);
}
The draw function
draw() {
var ctx = this.canvas.getContext('2d');
ctx.font = "15px Arial";
var seats = []
var tempOrderArrey = []
var orderSeatsClinet = []
var selectedSeatsClient = []
var numberOfSeats = 10
var numberOfRows = 10
var canvasWidth = this.innerWidth
var canvasHight = this.innerHeight
function Seat(x, y, w, h, id, line, seatNum, color) {
this.x = x - 160
this.y = y ;
this.w = w;
this.h = h;
this.id = id;
this.line = line
this.seatNo = seatNum + 1 ;
this.color = color
}
Seat.prototype.draw = function () {
ctx.fillStyle = this.color
ctx.fillRect(this.x, this.y, this.w, this.h)
}
function drawAll() {
for (var i = 0; i < seats.length; i++) {
seats[i].draw();
}
}
var id = 1;
var xPad = canvasWidth / 30
function addSeats(value, ch) {
for (let i = 0; i <= 15; i++)
seats.push(new Seat( (canvasWidth / 3) + (i * xPad), value, canvasWidth/ 37, canvasWidth/ 37, id++, ch, i, "#998515"));
}
var start = 60, diff = canvasWidth/30, ch = 0;
for (let i = 0; i < 2; i++) {
//60 + (40 * i)
addSeats(start + (diff * i), ch++);
}
drawAll()
The click event function
this.renderer.listen(this.canvasRef.nativeElement, 'click', (event) => {
let cX = event.layerX;
let cY = event.layerY;
const offsetLeft = this.canvasRef.nativeElement.offsetLeft;
const offsetTop = this.canvasRef.nativeElement.offsetTop;
this.cX = cX - offsetLeft;
this.cY = cY - offsetTop;
for (var i = 0; i < seats.length; i++) {
var s = seats[i];
if (cX >= s.x && cX < s.x + s.w && cY >= s.y && cY < s.y + s.h) {
if (s.color == '#998515') { // If green
tempOrderArrey.push({ "id": s.id, "seatNum": s.seatNo, "rowNum": s.line })
s.color = '#ff0000'
ctx.fillStyle = '#ff0000'
ctx.fillRect(s.x, s.y, s.w, s.h)
}
else if (s.color == '#ff0000') { // If red
tempOrderArrey = tempOrderArrey.filter(seat => seat.id != s.id);
ctx.fillStyle = '#998515'
s.color = '#998515'
ctx.fillRect(s.x, s.y, s.w, s.h)
}
}
this.tempOrderArrey = tempOrderArrey
}})
}
The reason is that each time you resize, renderer.listen is called again, so for one click there are many events being fired.
You need to make sure that you clear the listener before creating a new one
// Check if the reference exists
if (this.reference != null) {
this.reference; // Clear the listener
}
// Store a reference to the listener
this.reference = this.renderer.listen(this.canvasRef.nativeElement, 'click', (event) => {
Here is a fork of the StackBlitz

Creating the Butterfly curve with arrays

I have two questions, the first being how do I access the indexes within my array separately, because my console.log of [n][0] results in two values - x and y. Secondly, for the butterfly curve, https://en.wikipedia.org/wiki/Butterfly_curve_%28transcendental%29, how would I determine the values of t? and reiterate through a certain minimum and maximum. In need of logic support.
Here's my progress so far.
/*function drawButterFly(n){
c.beginPath();
console.log(n[2])
for (var i = 0; i < n.length; i++){
if (i === 0) {
c.moveTo();
} else {
c.lineTo();
}
c.stroke();
}
}*/
function butterFly() {
var r = 5;
var N = 3;
var value = [];
for (var a = 0.2; a < 2*Math.PI; a = a + 0.1){
value.push(a);
}
var t = value[Math.floor(Math.random()*value.length)];
var cos = r*Math.cos(t)*( (Math.exp(Math.cos(t))) - (2*Math.cos(4*t)) - (Math.sin(t/12)^5) );
var sin = r*Math.sin(t)*( (Math.exp(Math.cos(t))) - (2*Math.cos(4*t)) - (Math.sin(t/12)^5) );
var n = [];
for (var u = 0; u < N; u++){
var x = sin * -u;
var y = cos * -u;
n.push([x,y]);
}
drawButterFly(n);
}
Since you're pushing an array here: n.push([x,y]) you can access the x component of the first element with n[0][0] and the y component of the same element with n[0][1]
Example:
var n = [];
n.push( ["x", "y"] );
console.log( n[0][0] );
console.log( n[0][1] );
As for the useful values of t - in the image you've shown, you'll notice that the same butterfly is drawn several times at different sizes. To draw a complete butterfly, you need to use the range for t of [0..2pi]. If you want to draw two butterflies, you need to use the range [0..4pi]. That is it's cyclic over the same period that a circle is. Unlike a circle however, each cycle doesn't draw over the previous one.
Here's a quick and nasty example:
function byId(id) {
return document.getElementById(id);
}
window.addEventListener('load', onDocLoaded, false);
function onDocLoaded(evt) {
butterFly();
}
function butterFly() {
var pointArray = [];
var stepSize = 0.05; // ~125 steps for every 360°
var upperLimit = 4 * Math.PI;
var scale = 20;
for (var t = 0.0; t < upperLimit; t += stepSize) {
var xVal = Math.sin(t) * ((Math.exp(Math.cos(t))) - (2 * Math.cos(4 * t)) - (Math.pow(Math.sin(t / 12), 5)));
var yVal = Math.cos(t) * ((Math.exp(Math.cos(t))) - (2 * Math.cos(4 * t)) - (Math.pow(Math.sin(t / 12), 5)));
pointArray.push([scale * xVal, -scale * yVal]); // -1 value since screen-y direction is opposite direction to cartesian coords y
}
drawButterFly(pointArray);
}
function drawButterFly(pointArray) {
var can = byId('myCan');
var ctx = can.getContext('2d');
var originX, originY;
originX = can.width / 2;
originY = can.height / 2;
ctx.beginPath();
for (var i = 0; i < pointArray.length; i++) {
if (i === 0) {
ctx.moveTo(originX + pointArray[i][0], originX + pointArray[i][1]);
} else {
ctx.lineTo(originX + pointArray[i][0], originY + pointArray[i][1]);
}
}
ctx.closePath();
ctx.stroke();
}
canvas {
border: solid 1px red;
}
<canvas id='myCan' width='256' height='256' />
If I'm not mistaken, the Butterfly curve is given as a pair of parametric equations, meaning you increment t to get the next (x, y) points on your curve. In other words, your t is what you should be using in place of u in your code, and the range of values for t should be 0 .. 24*pi as that's the range in which sin(t / 12) has its unique values).
Here's a version that demonstrates the drawing of the curve to a canvas:
function getPoint(t, S, O) {
var cos_t = Math.cos(t);
var factor = Math.exp(cos_t) - 2 * Math.cos(4*t) - Math.pow(Math.sin(t/12), 5);
return {
x: S * Math.sin(t) * factor + O.x,
y: S * cos_t * factor + O.y
};
}
var canvas = document.getElementById("c");
canvas.width = 300;
canvas.height = 300;
var ctx = canvas.getContext("2d");
// First path
ctx.beginPath();
ctx.strokeStyle = 'blue';
var offset = {x:150, y:120};
var scale = 40;
var maxT = 24 * Math.PI;
var p = getPoint(0, scale, offset);
ctx.moveTo(p.x, canvas.height - p.y);
for (var t = 0.01; t <= maxT; t += 0.01) {
p = getPoint(t, scale, offset);
ctx.lineTo(p.x, canvas.height - p.y);
}
ctx.stroke();
#c {
border: solid 1px black;
}
<canvas id="c"></canvas>
One thing to note: canvases have y = 0 start at the top, so you need to inverse your y (i.e. canvas.height - y) to have your curve orient correctly.
UPDATE: Added animated version
As requested by royhowie, here's an animated version, using requestAnimationFrame:
function getPoint(t, S, O) {
var cos_t = Math.cos(t);
var factor = Math.exp(cos_t) - 2 * Math.cos(4*t) - Math.pow(Math.sin(t/12), 5);
return {
x: S * Math.sin(t) * factor + O.x,
y: S * cos_t * factor + O.y
};
}
var canvas = document.getElementById("c");
canvas.width = 300;
canvas.height = 300;
var ctx = canvas.getContext("2d");
var offset = {x:150, y:120};
var scale = 40;
var maxT = 24 * Math.PI;
var animationID;
var started = false;
var t = 0;
document.getElementById('start').addEventListener('click', function(e) {
e.preventDefault();
if (!started) {
animationID = requestAnimationFrame(animate);
started = true;
}
});
document.getElementById('pause').addEventListener('click', function(e) {
e.preventDefault();
if (started) {
cancelAnimationFrame(animationID);
started = false;
}
});
function animate() {
animationID = requestAnimationFrame(animate);
var p = getPoint(t, scale, offset);
if (t === 0) {
ctx.beginPath();
ctx.strokeStyle = 'blue';
ctx.moveTo(p.x, canvas.height - p.y);
t += 0.01;
} else if (t < maxT) {
ctx.lineTo(p.x, canvas.height - p.y);
ctx.stroke();
t += 0.01;
} else {
cancelAnimationFrame(animationID);
}
}
#c {
border: solid 1px black;
}
<div>
<button id="start">Start</button>
<button id="pause">Pause</button>
</div>
<canvas id="c"></canvas>
Question 1
For an arbitrary integer i, let n[i] = [xi, yi]. xi can then be accessed via n[i][0] and yi via n[i][1]
Question 2
For values of t, I am sure you're gonna want to use sub-integer values, so I recommend using a constant increment value representing the "resolution" of your graph.
Let's call it dt. Also, I'd advise changing your variable names from single letters to something more descriptive, like min_t and max_t, and instead of n I'm going to call your array points.
function drawButterFly(points){
for (var i = 0, n = points.count; i < n; ++i) {
var x = points[i][0];
var y = points[i][1];
...
}
}
function butterFly(min_t, max_t, dt, r) {
var points = [];
for (var t = min_t; t < max_t; t+=dt){
var x = r*Math.sin(t)*...
var y = r*Math.cos(t)*...
points.push([x,y]);
}
drawButterFly(points, dt);
}
I'm not sure what the other loop inside of that function was for, but if you need it, you can adapt from the pattern above.
Usage example: butterFly(0, 10, 0.01, 3) -> t goes from 0 to 10 with an increment of 0.01, and r=3
Regarding your first question it's a better option to replace the multidimensional array containing the x and y coordinates with an object. Then when iterating over the array you can check for the object values.
So instead of:
n.push([x,y]);
you should do:
m.push({
'xPos' : x,
'yPos' : y
})
Later you can access this by m.xPos or m.yPos
Then you can access the x and y values by object literal names.
Regarding the second question: for a good pseudo code implementation of butterfly curves you might check Paul Burke site: http://paulbourke.net/geometry/butterfly/. So t in your case is:
t = i * 24.0 * PI / N;
As you see t is a parametric value which got incremented on each step when iterating over the array.

Why is this clearInterval not reaching the var, though it is set as global?

JSFiddle
After each circle object is created, it should increase in size while the mouse is down, and stop when mouse is up. clearInterval doesn't seem to reach the internal variable "growLoop" even though it's supposed to be global by declaring it first(which was the advice on many other posts about this same issue). In the console it shows growLoop undefined, but it's defined in line 95, right?
Also, the time interval seems to decrease with every new circle created, and they grow faster. How could the value of setInterval change?
//set up canvas
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var circles = [];
//create circle
function create(location) {
circles.push({
x: location.x,
y: location.y,
radius: 10,
color: '#' + Math.floor(Math.random() * 16777215).toString(16)
});
}
//figure out mouse position
var rect = document.getElementById("canvas").getBoundingClientRect();
// Get canvas offset on page
var offset = {
x: rect.left,
y: rect.top
};
function isOnCanvas(a) {
if ((a.x >= 0 && a.x <= rect.width) && (a.y >= 0 && a.y <= rect.height)) {
return true;
}
return false;
}
function isOnCircle(a) {
var i = 0,
l = circles.length,
x, y, d, c;
for (; i < l; ++i) {
c = circles[i];
x = a.x - c.x;
y = a.y - c.y;
d = (a.radius || 10) + c.radius;
if (x * x + y * y <= d * d) {
return true;
}
}
return false;
}
// draw all circles
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (var i = 0; i < circles.length; i++) {
var p = circles[i];
ctx.beginPath();
ctx.arc(p.x, p.y, p.radius, 0, 2 * Math.PI);
ctx.fillStyle = p.color;
ctx.fill();
}
}
//make last drawn circle 1px bigger
function grow(){
var a = circles[circles.length-1];
a.radius += 1;
}
//find percentage of canvas filled in
var totalSpace = canvas.width * canvas.height;
var totalFilled = function () {
total = 0;
for (var i = 0; i < circles.length; i++) {
var p = circles[i];
total += Math.PI * Math.pow(p.radius, 2);
}
return total;
console.log(total);
}
function findPercentage() {
return (totalFilled() / totalSpace) * 100;
}
function updateInfo() {
percentage = findPercentage();
document.getElementById("percentage").innerHTML = "You've filled in " + percentage.toFixed(1) + "%";
}
//do all the stuff
var animate = function(){
draw();
grow();
updateInfo();}
var growLoop = 0;
window.onmousedown = function (e) {
// get event location on page offset by canvas location
var location = {
x: e.pageX - offset.x,
y: e.pageY - offset.y
};
if (isOnCanvas(location) && !isOnCircle(location)) {
create(location);
var growLoop = setInterval(animate, 100);
}
};
window.onmouseup = function (e) {
clearInterval(growLoop);
}
window.onmouseout = function (e) {
clearInterval(growLoop);
}
var growLoop = setInterval(animate, 100);
By adding var here you are declaring an internal variable also named growLoop and not assigning to the global. Remove var.
growLoop = setInterval(animate, 100);
http://jsfiddle.net/SeAGU/85/

Categories

Resources