Creating the Butterfly curve with arrays - javascript

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.

Related

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

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>

Dynamic Wavy Path/Border

There is something I need to build, but my math ability is not up to par. What I am looking to build is something like this demo, but I need it to be a hybrid of a circle and polygon instead of a line, so to speak. The black line should be dynamic and randomly generated that basically acts as a border on the page.
Currently, I am dissecting this answer with the aim of hopefully being able to transpose it into this, but I am having massive doubts that I will be able to figure this out.
Any idea how to do this or can anybody explain the mathematics?
Below are my notes about the code from the answer I linked above.
var
cw = cvs.width = window.innerWidth,
ch = cvs.height = window.innerHeight,
cx = cw / 2,
cy = ch / 2,
xs = Array(),
ys = Array(),
npts = 20,
amplitude = 87, // can be val from 1 to 100
frequency = -2, // can be val from -10 to 1 in steps of 0.1
ctx.lineWidth = 4
// creates array of coordinates that
// divides page into regular portions
// creates array of weights
for (var i = 0; i < npts; i++) {
xs[i] = (cw/npts)*i
ys[i] = 2.0*(Math.random()-0.5)*amplitude
}
function Draw() {
ctx.clearRect(0, 0, cw, ch);
ctx.beginPath();
for (let x = 0; x < cw; x++) {
y = 0.0
wsum = 0.0
for (let i = -5; i <= 5; i++) {
xx = x; // 0 / 1 / 2 / to value of screen width
// creates sequential sets from [-5 to 5] to [15 to 25]
ii = Math.round(x/xs[1]) + i
// `xx` is a sliding range with the total value equal to client width
// keeps `ii` within range of 0 to 20
if (ii < 0) {
xx += cw
ii += npts
}
if (ii >= npts){
xx -= cw
ii -= npts
}
// selects eleven sequential array items
// which are portions of the screen width and height
// to create staggered inclines in increments of those portions
w = Math.abs(xs[ii] - xx)
// creates irregular arcs
// based on the inclining values
w = Math.pow(w, frequency)
// also creates irregular arcs therefrom
y += w*ys[ii];
// creates sets of inclining values
wsum += w;
}
// provides a relative position or weight
// for each y-coordinate in the total path
y /= wsum;
//y = Math.sin(x * frequency) * amplitude;
ctx.lineTo(x, y+cy);
}
ctx.stroke();
}
Draw();
This is my answer. Please read the comments in the code. I hope this is what you need.
// initiate the canvas
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
let cw = (canvas.width = 600),
cx = cw / 2;
let ch = (canvas.height = 400),
cy = ch / 2;
ctx.fillStyle = "white"
// define the corners of an rectangle
let corners = [[100, 100], [500, 100], [500, 300], [100, 300]];
let amplitud = 20;// oscilation amplitude
let speed = 0.01;// the speed of the oscilation
let points = []; // an array of points to draw the curve
class Point {
constructor(x, y, hv) {
// the point is oscilating around this point (cx,cy)
this.cx = x;
this.cy = y;
// the current angle of oscilation
this.a = Math.random() * 2 * Math.PI;
this.hv = hv;// a variable to know if the oscilation is horizontal or vertical
this.update();
}
// a function to update the value of the angle
update() {
this.a += speed;
if (this.hv == 0) {
this.x = this.cx;
this.y = this.cy + amplitud * Math.cos(this.a);
} else {
this.x = this.cx + amplitud * Math.cos(this.a);
this.y = this.cy;
}
}
}
// a function to divide a line that goes from a to b in n segments
// I'm using the resulting points to create a new point object and push this new point into the points array
function divide(n, a, b) {
for (var i = 0; i <= n; i++) {
let p = {
x: (b[0] - a[0]) * i / n + a[0],
y: (b[1] - a[1]) * i / n + a[1],
hv: b[1] - a[1]
};
points.push(new Point(p.x, p.y, p.hv));
}
}
divide(10, corners[0], corners[1]);points.pop();
divide(5, corners[1], corners[2]);points.pop();
divide(10, corners[2], corners[3]);points.pop();
divide(5, corners[3], corners[0]);points.pop();
// this is a function that takes an array of points and draw a curved line through those points
function drawCurves() {
//find the first midpoint and move to it
let p = {};
p.x = (points[points.length - 1].x + points[0].x) / 2;
p.y = (points[points.length - 1].y + points[0].y) / 2;
ctx.beginPath();
ctx.moveTo(p.x, p.y);
//curve through the rest, stopping at each midpoint
for (var i = 0; i < points.length - 1; i++) {
let mp = {};
mp.x = (points[i].x + points[i + 1].x) / 2;
mp.y = (points[i].y + points[i + 1].y) / 2;
ctx.quadraticCurveTo(points[i].x, points[i].y, mp.x, mp.y);
}
//curve through the last point, back to the first midpoint
ctx.quadraticCurveTo(
points[points.length - 1].x,
points[points.length - 1].y,
p.x,
p.y
);
ctx.stroke();
ctx.fill();
}
function Draw() {
window.requestAnimationFrame(Draw);
ctx.clearRect(0, 0, cw, ch);
points.map(p => {
p.update();
});
drawCurves();
}
Draw();
canvas{border:1px solid; background:#6ab150}
<canvas></canvas>

How to make blur effect particles in javascript

Hi I want to make a blur effect particle like this:
Can I use shadowBlur and shadowOffsetX/shadowOffsetY to do this? The actual shine will glow and fade a little bit repeatedly, so if I have to write some kind of animation how can I achieve this?
I have tried this code (jsfiddle example) but it doesn't look like the effect. So I wonder how to blur and glow the particle at the same time?
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
const ra = window.requestAnimationFrame
|| window.webkitRequestAnimationFrame
|| window.mozRequestAnimationFrame
|| window.oRequestAnimationFrame
|| window.msRequestAnimationFrame
|| function(callback) {
window.setTimeout(callback, 1000 / 60);
};
class Particle {
constructor(options) {
this.ctx = options.context;
this.x = options.x;
this.y = options.y;
this.radius = options.radius;
this.lightSize = this.radius;
this.color = options.color;
this.lightDirection = true;
}
glow() {
const lightSpeed = 0.5;
this.lightSize += this.lightDirection ? lightSpeed : -lightSpeed;
if (this.lightSize > this.radius || this.lightSize < this.radius) {
this.lightDirection = !this.lightDirection;
}
}
render() {
this.ctx.clearRect(0, 0, canvas.width, canvas.height);
this.glow();
this.ctx.globalAlpha = 0.5;
this.ctx.fillStyle = this.color;
this.ctx.beginPath();
this.ctx.arc(this.x, this.y, this.lightSize,
0, Math.PI * 2
);
this.ctx.fill();
this.ctx.globalAlpha = 0.62;
this.ctx.beginPath();
this.ctx.arc(this.x, this.y, this.radius * 0.7, 0, Math.PI * 2);
this.ctx.shadowColor = this.color;
this.ctx.shadowBlur = 6;
this.ctx.shadowOffsetX = 0;
this.ctx.shadowOffsetY = 0;
this.ctx.fill();
}
}
var particle = new Particle({
context: ctx,
x: 60,
y: 80,
radius: 12,
color: '#4d88ff'
});
function run() {
particle.render();
ra(run);
}
run();
<canvas id='canvas'></canvas>
There are several ways to do this. For a particle system my option is to pre render the blur using a blur filter. A common filter is the convolution filter. It uses a small array to determine the amount neighboring pixels contribute to each pixel of the image. You are best to look up convolution functions to understand it.
Wiki Convolution and Wiki Gaussian blur for more info.
I am not much of a fan of the standard Gaussian blur or the convolution filter used so in the demo snippet below you can find my version that I think creates a much better blur. The convolution blur filter is procedurally created and is in the imageTools object.
To use create a filter pass an object with properties size the blur amount in pixels and power is the strength. Lower powers is less spread on the blur.
// image must be loaded or created
var blurFilter = imageTools.createBlurConvolutionArray({size:17,power:1}); // size must be greater than 2 and must be odd eg 3,5,7,9...
// apply the convolution filter on the image. The returned image may be a new
//image if the input image does not have a ctx property pointing to a 2d canvas context
image = imageTools.applyConvolutionFilter(image,blurFilter);
In the demo I create a image, draw a circle on it, copy it and pad it so that there is room for the blur. Then create a blur filter and apply it to the image.
When I render the particles I first draw all the unblurred images, then draw the blurred copies with the ctx.globalCompositeOperation = "screen"; so that they have a shine. To vary the amount of shine I use the ctx.globalAlpha to vary the intensity of the rendered blurred image. To improve the FX I have drawn the blur image twice, once with oscillating scale and next at fixed scale and alpha.
The demo is simple, image tools can be found at the top. Then there is some stuff to setup the canvas and handle resize event. Then there is the code that creates the images, and apply the filters. Then starts the render adds some particles and renders everything.
Look in the function drawParticles for how I draw everything.
imageTools has all the image functions you will need. The imageTools.applyConvolutionFilter will apply any filter (sharpen, outline, and many more) you just need to create the appropriate filter. The apply uses the photon count colour model so gives a very high quality result especially for blurs type effects. (though for sharpen you may want to get in and change the squaring of the RGB values, I personally like it other do not)
The blur filter is not fast so if you apply it to larger images It would be best that you break it up in so you do not block the page execution.
A cheap way to get a blur is to copy the image to blur to a smaller version of itself, eg 1/4 then render it scaled back to normal size, the canvas will apply bilinear filtering on the image give a blur effect. Not the best quality but for most situations it is indistinguishable from the more sophisticated blur that I have presented.
UPDATE
Change the code so that the particles have a bit of a 3dFX to show that the blur can work up to larger scales. The blue particles are 32 by 32 image and the blur is 9 pixels with the blur image being 50by 50 pixels.
var imageTools = (function () {
var tools = {
canvas : function (width, height) { // create a blank image (canvas)
var c = document.createElement("canvas");
c.width = width;
c.height = height;
return c;
},
createImage : function (width, height) {
var image = this.canvas(width, height);
image.ctx = image.getContext("2d");
return image;
},
image2Canvas : function (img) {
var image = this.canvas(img.width, img.height);
image.ctx = image.getContext("2d");
image.drawImage(img, 0, 0);
return image;
},
padImage : function(img,amount){
var image = this.canvas(img.width + amount * 2, img.height + amount * 2);
image.ctx = image.getContext("2d");
image.ctx.drawImage(img, amount, amount);
return image;
},
getImageData : function (image) {
return (image.ctx || (this.image2Canvas(image).ctx)).getImageData(0, 0, image.width, image.height);
},
putImageData : function (image, imgData){
(image.ctx || (this.image2Canvas(image).ctx)).putImageData(imgData,0, 0);
return image;
},
createBlurConvolutionArray : function(options){
var i, j, d; // misc vars
var filterArray = []; // the array to create
var size = options.size === undefined ? 3: options.size; // array size
var center = Math.floor(size / 2); // center of array
// the power ? needs descriptive UI options
var power = options.power === undefined ? 1: options.power;
// dist to corner
var maxDist = Math.sqrt(center * center + center * center);
var dist = 0; // distance sum
var sum = 0; // weight sum
var centerWeight; // center calculated weight
var totalDistance; // calculated total distance from center
// first pass get the total distance
for(i = 0; i < size; i++){
for(j = 0; j < size; j++){
d = (maxDist-Math.sqrt((center-i)*(center-i)+(center-j)*(center-j)));
d = Math.pow(d,power)
dist += d;
}
}
totalDistance = dist; // total distance to all points;
// second pass get the total weight of all but center
for(i = 0; i < size; i++){
for(j = 0; j < size; j++){
d = (maxDist-Math.sqrt((center-i)*(center-i)+(center-j)*(center-j)));
d = Math.pow(d,power)
d = d/totalDistance;
sum += d;
}
}
var scale = 1/sum;
sum = 0; // used to check
for(i = 0; i < size; i++){
for(j = 0; j < size; j++){
d = (maxDist-Math.sqrt((center-i)*(center-i)+(center-j)*(center-j)));
d = Math.pow(d,power)
d = d/totalDistance;
filterArray.push(d*scale);
}
}
return filterArray;
},
applyConvolutionFilter : function(image,filter){
imageData = this.getImageData(image);
imageDataResult = this.getImageData(image);
var w = imageData.width;
var h = imageData.height;
var data = imageData.data;
var data1 = imageDataResult.data;
var side = Math.round(Math.sqrt(filter.length));
var halfSide = Math.floor(side/2);
var r,g,b,a,c;
for(var y = 0; y < h; y++){
for(var x = 0; x < w; x++){
var ind = y*4*w+x*4;
r = 0;
g = 0;
b = 0;
a = 0;
for (var cy=0; cy<side; cy++) {
for (var cx=0; cx<side; cx++) {
var scy = y + cy - halfSide;
var scx = x + cx - halfSide;
if (scy >= 0 && scy < h && scx >= 0 && scx < w) {
var srcOff = (scy*w+scx)*4;
var wt = filter[cy*side+cx];
r += data[srcOff+0] * data[srcOff+0] * wt;
g += data[srcOff+1] * data[srcOff+1] * wt;
b += data[srcOff+2] * data[srcOff+2] * wt;
a += data[srcOff+3] * data[srcOff+3] * wt;
}
}
}
data1[ind+0] = Math.sqrt(Math.max(0,r));
data1[ind+1] = Math.sqrt(Math.max(0,g));
data1[ind+2] = Math.sqrt(Math.max(0,b));
data1[ind+3] = Math.sqrt(Math.max(0,a));
}
}
return this.putImageData(image,imageDataResult);
}
};
return tools;
})();
/** SimpleFullCanvasMouse.js begin **/
const CANVAS_ELEMENT_ID = "canv";
const U = undefined;
var w, h, cw, ch; // short cut vars
var canvas, ctx;
var globalTime = 0;
var createCanvas, resizeCanvas, setGlobals;
var L = typeof log === "function" ? log : function(d){ console.log(d); }
createCanvas = function () {
var c,cs;
cs = (c = document.createElement("canvas")).style;
c.id = CANVAS_ELEMENT_ID;
cs.position = "absolute";
cs.top = cs.left = "0px";
cs.zIndex = 1000;
document.body.appendChild(c);
return c;
}
resizeCanvas = function () {
if (canvas === U) { canvas = createCanvas(); }
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
ctx = canvas.getContext("2d");
if (typeof setGlobals === "function") { setGlobals(); }
}
setGlobals = function(){
cw = (w = canvas.width) / 2; ch = (h = canvas.height) / 2;
if(particles && particles.length > 0){
particles.length = 0;
}
}
resizeCanvas(); // create and size canvas
window.addEventListener("resize",resizeCanvas); // add resize event
const IMAGE_SIZE = 32;
const IMAGE_SIZE_HALF = 16;
const GRAV = 2001;
const NUM_PARTICLES = 90;
var background = imageTools.createImage(8,8);
var grad = ctx.createLinearGradient(0,0,0,8);
grad.addColorStop(0,"#000");
grad.addColorStop(1,"#048");
background.ctx.fillStyle = grad;
background.ctx.fillRect(0,0,8,8);
var circle = imageTools.createImage(IMAGE_SIZE,IMAGE_SIZE);
circle.ctx.fillStyle = "#5BF";
circle.ctx.arc(IMAGE_SIZE_HALF, IMAGE_SIZE_HALF, IMAGE_SIZE_HALF -2,0, Math.PI * 2);
circle.ctx.fill();
var blurFilter = imageTools.createBlurConvolutionArray({size:9,power:1}); // size must be greater than 2 and must be odd eg 3,5,7,9...
var blurCircle = imageTools.padImage(circle,9);
blurCircle = imageTools.applyConvolutionFilter(blurCircle,blurFilter)
var sun = imageTools.createImage(64,64);
grad = ctx.createRadialGradient(32,32,0,32,32,32);
grad.addColorStop(0,"#FF0");
grad.addColorStop(1,"#A40");
sun.ctx.fillStyle = grad;
sun.ctx.arc(32,32,32 -2,0, Math.PI * 2);
sun.ctx.fill();
var sunBlur = imageTools.padImage(sun,17);
blurFilter = imageTools.createBlurConvolutionArray({size:17,power:1}); // size must be greater than 2 and must be odd eg 3,5,7,9...
sunBlur = imageTools.applyConvolutionFilter(sunBlur,blurFilter);
var particles = [];
var createParticle = function(x,y,dx,dy){
var dir = Math.atan2(y-ch,x-cw);
var dist = Math.sqrt(Math.pow(y-ch,2)+Math.pow(x-cw,2));
var v = Math.sqrt(GRAV / dist); // get apporox orbital speed
return {
x : x,
y : y,
dx : dx + Math.cos(dir + Math.PI/2) * v, // set orbit speed at tangent
dy : dy + Math.sin(dir + Math.PI/2) * v,
s : (Math.random() + Math.random() + Math.random())/4 + 0.5, // scale
v : (Math.random() + Math.random() + Math.random()) / 3 + 2, // glow vary rate
};
}
var depthSort = function(a,b){
return b.y - a.y;
}
var updateParticles = function(){
var i,p,f,dist,dir;
for(i = 0; i < particles.length; i ++){
p = particles[i];
dist = Math.sqrt(Math.pow(cw-p.x,2)+Math.pow(ch-p.y,2));
dir = Math.atan2(ch-p.y,cw-p.x);
f = GRAV * 1 / (dist * dist);
p.dx += Math.cos(dir) * f;
p.dy += Math.sin(dir) * f;
p.x += p.dx;
p.y += p.dy;
p.rx = ((p.x - cw ) / (p.y + h)) * h + cw;
p.ry = ((p.y - ch ) / (p.y + h)) * h * -0.051+ ch;
//p.ry = ((h-p.y) - ch) * 0.1 + ch;
p.rs = (p.s / (p.y + h)) * h
}
particles.sort(depthSort)
}
var drawParticles = function(){
var i,j,p,f,dist,dir;
// draw behind the sun
for(i = 0; i < particles.length; i ++){
p = particles[i];
if(p.y - ch < 0){
break;
}
ctx.setTransform(p.rs,0,0,p.rs,p.rx,p.ry);
ctx.drawImage(circle,-IMAGE_SIZE_HALF,-IMAGE_SIZE_HALF);
}
// draw glow for behind the sun
ctx.globalCompositeOperation = "screen";
var iw = -blurCircle.width/2;
for(j = 0; j < i; j ++){
p = particles[j];
ctx.globalAlpha = ((Math.sin(globalTime / (50 * p.v)) + 1) / 2) * 0.6 + 0.4;
var scale = (1-(Math.sin(globalTime / (50 * p.v)) + 1) / 2) * 0.6 + 0.6;
ctx.setTransform(p.rs * 1.5 * scale,0,0,p.rs * 1.5* scale,p.rx,p.ry);
ctx.drawImage(blurCircle,iw,iw);
// second pass to intensify the glow
ctx.globalAlpha = 0.7;
ctx.setTransform(p.rs * 1.1,0,0,p.rs * 1.1,p.rx,p.ry);
ctx.drawImage(blurCircle,iw,iw);
}
// draw the sun
ctx.globalCompositeOperation = "source-over";
ctx.globalAlpha = 1;
ctx.setTransform(1,0,0,1,cw,ch);
ctx.drawImage(sun,-sun.width/2,-sun.height/2);
ctx.globalAlpha = 1;
ctx.globalCompositeOperation = "screen";
ctx.setTransform(1,0,0,1,cw,ch);
ctx.drawImage(sunBlur,-sunBlur.width/2,-sunBlur.height/2);
var scale = Math.sin(globalTime / 100) *0.5 + 1;
ctx.globalAlpha = (Math.cos(globalTime / 100) + 1) * 0.2 + 0.4;;
ctx.setTransform(1 + scale,0,0,1 + scale,cw,ch);
ctx.drawImage(sunBlur,-sunBlur.width/2,-sunBlur.height/2);
ctx.globalAlpha = 1;
ctx.globalCompositeOperation = "source-over";
// draw in front the sun
for(j = i; j < particles.length; j ++){
p = particles[j];
if(p.y > -h){ // don't draw past the near view plane
ctx.setTransform(p.rs,0,0,p.rs,p.rx,p.ry);
ctx.drawImage(circle,-IMAGE_SIZE_HALF,-IMAGE_SIZE_HALF);
}
}
ctx.globalCompositeOperation = "screen";
var iw = -blurCircle.width/2;
for(j = i; j < particles.length; j ++){
p = particles[j];
if(p.y > -h){ // don't draw past the near view plane
ctx.globalAlpha = ((Math.sin(globalTime / (50 * p.v)) + 1) / 2) * 0.6 + 0.4;
var scale = (1-(Math.sin(globalTime / (50 * p.v)) + 1) / 2) * 0.6 + 0.6;
ctx.setTransform(p.rs * 1.5 * scale,0,0,p.rs * 1.5* scale,p.rx,p.ry);
ctx.drawImage(blurCircle,iw,iw);
// second pass to intensify the glow
ctx.globalAlpha = 0.7;
ctx.setTransform(p.rs * 1.1,0,0,p.rs * 1.1,p.rx,p.ry);
ctx.drawImage(blurCircle,iw,iw);
}
}
ctx.globalCompositeOperation = "source-over";
}
var addParticles = function(count){
var ww = (h-10)* 2;
var cx = cw - ww/2;
var cy = ch - ww/2;
for(var i = 0; i < count; i ++){
particles.push(createParticle(cx + Math.random() * ww,cy + Math.random() * ww, Math.random() - 0.5, Math.random() - 0.5));
}
}
function display(){ // put code in here
if(particles.length === 0){
addParticles(NUM_PARTICLES);
}
ctx.setTransform(1,0,0,1,0,0); // reset transform
ctx.globalAlpha = 1; // reset alpha
ctx.drawImage(background,0,0,w,h)
updateParticles();
drawParticles();
ctx.globalAlpha = 1;
ctx.globalCompositeOperation = "source-over";
}
function update(timer){ // Main update loop
globalTime = timer;
display(); // call demo code
requestAnimationFrame(update);
}
requestAnimationFrame(update);
/** SimpleFullCanvasMouse.js end **/

How to ease between two y-coordinates?

For a school assignment we have to make a graph in Javascript.
The teacher would like to see some animated graphs. So I build a graph about my Tweets in one week, but cannot find how to ease between two y-coordinates.
You can find my project here on jsfiddle, or on this website.
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var form = document.getElementById("form");
var data = [];
var huidigeYpos = [];
var nieuweYpos = [];
var count = [];
var snelheid = 0;
function init(){
ctx.fillStyle="rgb(255,255,255)";
ctx.fillRect(0,0,canvas.width,canvas.height);
ctx.translate(0, 445);
for(var i = 0; i < 7; i++){
data[i] = form[i].value*30;
}
draw();
}
function draw(){
ctx.fillStyle="rgb(255,255,255)";
ctx.fillRect(0,0,canvas.width,-canvas.height);
ctx.beginPath();
ctx.moveTo(0,0);
for(var i = 0; i <= 750; i += 125){
ctx.lineTo(i,-data[i/125]);
huidigeYpos.push((data[i/125]));
}
if(huidigeYpos.length > 7){
huidigeYpos.splice(0, 7);
}
ctx.lineTo(750,0);
ctx.closePath();
ctx.fillStyle="#0084FF";
ctx.fill();
}
function invullen(){
for(var i = 0; i < 7; i++){
data[i] = form[i].value*30;
}
draw();
}
function drawRandomGraph(){
for(var i = 0; i < 7; i++){
form[i].value = Math.round(Math.random()*10);
nieuweYpos.push(form[i].value*30);
}
if(nieuweYpos.length > 7){
nieuweYpos.splice(0, 7);
}
invullen();
}
init();
Thanks in advance!
You can use interpolation in combination with a easing-function. Standard interpolation between two points, aka lerping, is simple enough:
p = p1 + (p2 - p1) * t
where t is in the range [0, 1]
By injecting a easing-function for t, which also is in the [0, 1] range, you can ease the transition:
...
y = y1 + (y2 - y1) * easeInOut(t);
...
function easeInOut(t) {return t<.5 ? 4*t*t*t : (t-1)*(2*t-2)*(2*t-2)+1}
There are several variations of easing functions, the above is cubic. You can find more here as well as the popular Penner versions.
For your case you would just update y2 with the new target value as use the old y2 as y1, then lerp/ease between them for each x point using the same t value.
The demo below shows how to use these, integrate as you want.
Example
var ctx = document.querySelector("canvas").getContext("2d"),
y1 = 10, y2 = 140, // source and destination positions
current = 0, max = 50, delta = 1; // counters for calculating/animating t
(function loop() {
// calc t
current += delta;
var t = current / max; // normalize so we get [0, 1]
if (t <= 0 || t >= 1) delta = -delta; // to ping-pong the animation for demo
// calc lerp linear
var yl = lerp(y1, y2, t), // linear
ye = lerp(y1, y2, easeInOut(t)); // with easing
// draw some graphics to show (linear : easing)
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.fillRect(20, yl - 10, 20, 20);
ctx.fillRect(50, ye - 10, 20, 20);
requestAnimationFrame(loop);
})();
function lerp(p1, p2, t) {
return p1 + (p2 - p1) * t
}
function easeInOut(t) {
return t<.5 ? 4*t*t*t : (t-1)*(2*t-2)*(2*t-2)+1
}
<canvas></canvas>

Overlap/hit test in javascript

Im trying to get a hit test/overlap test to work in the below code which creates 200 circles in a random position on a canvas. I am trying to store the positions of the circles in an array and then checking that array each time another circle is created in the for loop, if the randomly created x and y is too close to a already created circle it should keep getting a new random x and y until it isnt too close to an already created circle.
I just cant get it too work in the while loop.
Any help please...
Thanks
<script type="text/javascript">
document.addEventListener("DOMContentLoaded", canvasDraw);
function canvasDraw () {
var c = document.getElementById("canvas");
var w = window.innerWidth;
var h = window.innerHeight;
c.width = w;
c.height = h;
var ctx = c.getContext("2d");
ctx.clearRect(0,0,c.width, c.height);
var abc = 0;
var colours = new Array ("rgb(0,100,0)", "rgb(51,102,255)");
var positions = new Array();
function hitTest(x, y) {
for(var p in positions) {
pp = p.split(",");
pp[0] = parseInt(pp[0]);
pp[1] = parseInt(pp[1]);
if(((x > (pp[0] - 24)) && (x < (pp[0] + 24))) && ((y > (pp[1] - 24)) && (y < (pp[1] + 24)))) {
return true;
}
}
return false;
}
//Loop 200 times for 200 circles
for (i=0; i<200; i++) {
var x = Math.floor(Math.random()*c.width);
var y = Math.floor(Math.random()*c.height);
while(hitTest(x, y) == true){
var x = Math.floor(Math.random()*c.width);
var y = Math.floor(Math.random()*c.height);
}
var pos = x.toString() + "," + y.toString();
positions.push(pos);
var radius = 10;
var r = radius.toString();
var b = colours[Math.floor(Math.random()*colours.length)];
circle(ctx,x,y, radius, b);
}
}
function circle (ctx, x, y, radius, b) {
ctx.fillStyle = b;
ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.PI*2, true);
ctx.closePath();
ctx.fill();
}
</script>
Some things before beginning:
Don't create arrays with new Array(), unless you specify the initial length. Use [];
Don't iterate through arrays using for...in: use a standard for with a counter. This is more a good practice;
Converting numbers into strings and converting back to numbers is useless and expensive. Use a small array to store both values;
Don't use "magic numbers", i.e. number with a precise value but hard to recognize immediately. Use named "constants" or put a comment near each of them telling what they mean, for future maintenance.
Ok, let's see the code.
if(((x > (pp[0] - 24)) && (x < (pp[0] + 24))) && ((y > (pp[1] - 24)) && (y < (pp[1] + 24))))
Honestly, what is this? I'd call it a cranky and obscure snippet. Recall what you've learnt at school:
var dx = pp[0] - x, dy = pp[1] - y;
if (dx * dx + dy * dy < 400) return true;
Isn't it much clearer?
Let's see the whole function:
function canvasDraw () {
var c = document.getElementById("canvas");
var w = window.innerWidth;
var h = window.innerHeight;
c.width = w;
c.height = h;
var ctx = c.getContext("2d");
ctx.clearRect(0,0,c.width, c.height);
// Lolwut?
// var abc = 0;
var colours = ["rgb(0,100,0)", "rgb(51,102,255)"];
var positions = [];
function hitTest(x, y) {
for (var j = 0; j < positions.length; j++) {
var pp = positions[j];
var dx = pp[0] - x, dy = pp[1] - y;
if (dx * dx + dy * dy < 400) return true;
}
return false;
}
// You declare the radius once and for all
var radius = 10;
// Declare the local scoped variables. You forgot i
var x, y, i;
for (i=0; i<200; i++) {
// How about a do...while instead of a while?
do {
var x = Math.floor(Math.random()*c.width);
var y = Math.floor(Math.random()*c.height);
// Testing with === is faster, always do it if you know the type
// I'll let it here, but if the type is boolean, you can avoid testing
// at all, as in while (hitTest(x, y));
} while (hitTest(x, y) === true);
positions.push([x, y]);
// This instruction is useless
// var r = radius.toString();
var b = colours[Math.floor(Math.random()*colours.length)];
circle(ctx,x,y, radius, b);
}
}
BEWARE, though, that depending on your canvas size, there could be no more room for another circle, so it could end in an infinite loop. Try to put 200 circles with a radius of 10 inside a 40x40 box...
There should be another test to do, and that could be complicated.

Categories

Resources