Overlap/hit test in javascript - 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.

Related

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 draw multiple paths for a moving image on canvas

I´m just beginning to learn Html5 canvas, the main idea is to make an image move through multiple lines, creating paths, the total distance of the lines should be 10km, so i will be using 10 lines of 1km, using a javascript prompt, the user will say how many km or mts the image will advance.
I think i´ll have to draw ten paths of lines, making the path of the image, but i still don´t know how to move the image through all the lines.
The following code, move the image once, with no line to follow, this is what i have so far:
$(function(){
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var requestAnimationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function (callback) {
window.setTimeout(callback, 1000 / 60);
};
var pathArray = []; var enx = prompt("coordenada en x");
var eny = prompt("coordenada en y");
var num1 = parseInt(enx);
var num2 = parseInt(eny);
// Coordenada inicial
pathArray.push({
x: 50,
y: 50
});
pathArray.push({
x: num1,
y: num2
});
var polypoints = makePolyPoints(pathArray);
var width = 2;
var height = 5;
var position = 0;
var speed = 2;
animate();
var fps = 60;
function animate() {
setTimeout(function () {
requestAnimFrame(animate);
// calcular nueva posicion
position += speed;
if (position > polypoints.length - 1) {
return;
}
var pt = polypoints[position];
// dibujar
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.save();
ctx.beginPath();
ctx.translate(pt.x, pt.y);
// Objeto de la imagen del corredor
img = new Image();
// url de imagen
img.src = "img/mono_animar.png";
ctx.drawImage(img,0,0);
ctx.restore();
}, 1000 / fps);
}
function makePolyPoints(pathArray)
{
var points = [];
for (var i = 1; i < pathArray.length; i++) {
var startPt = pathArray[i - 1];
var endPt = pathArray[i];
var dx = endPt.x - startPt.x;
var dy = endPt.y - startPt.y;
for (var n = 0; n <= 100; n++)
{
var x = startPt.x + dx * n / 100;
var y = startPt.y + dy * n / 100;
points.push({
x: x,
y: y
});
}
}
return (points);}});
But i need something like this:
http://jsfiddle.net/ExpertSystem/qxgkc/
Except that the user will type a number from 1 to 12km in order to move the image along the path lines.
You could, in the draw loop implement a "line drawing algorithm" that does not exactly draw a line but draws an item at a place where that point would be.
function line(x0, y0, x1, y1){
var dx = Math.abs(x1-x0);
var dy = Math.abs(y1-y0);
var sx = (x0 < x1) ? 1 : -1;
var sy = (y0 < y1) ? 1 : -1;
var err = dx-dy;
while(true){ // put draw loop here.
drawImage(image,x0,y0);//setPixel(x0,y0); // Do what you need to for this
if ((x0==x1) && (y0==y1)) break;
var e2 = 2*err;
if (e2 >-dy){ err -= dy; x0 += sx; }
if (e2 < dx){ err += dx; y0 += sy; }
}
}
code taken from: Bresenham algorithm in Javascript
I would suggest using a library like p5.js to do something like this.
http://p5js.org

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.

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>

dotted stroke in <canvas>

I guess it is not possible to set stroke property such as CSS which is quite easy. With CSS we have dashed, dotted, solid but on canvas when drawing lines/or strokes this doesn't seem to be an option. How have you implemented this?
I've seen some examples but they are really long for such a silly function.
For example:
http://groups.google.com/group/javascript-information-visualization-toolkit/browse_thread/thread/22000c0d0a1c54f9?pli=1
Fun question! I've written a custom implementation of dashed lines; you can try it out here. I took the route of Adobe Illustrator and allow you to specify an array of dash/gap lengths.
For stackoverflow posterity, here's my implementation (slightly altered for s/o line widths):
var CP = window.CanvasRenderingContext2D && CanvasRenderingContext2D.prototype;
if (CP && CP.lineTo){
CP.dashedLine = function(x,y,x2,y2,dashArray){
if (!dashArray) dashArray=[10,5];
if (dashLength==0) dashLength = 0.001; // Hack for Safari
var dashCount = dashArray.length;
this.moveTo(x, y);
var dx = (x2-x), dy = (y2-y);
var slope = dx ? dy/dx : 1e15;
var distRemaining = Math.sqrt( dx*dx + dy*dy );
var dashIndex=0, draw=true;
while (distRemaining>=0.1){
var dashLength = dashArray[dashIndex++%dashCount];
if (dashLength > distRemaining) dashLength = distRemaining;
var xStep = Math.sqrt( dashLength*dashLength / (1 + slope*slope) );
if (dx<0) xStep = -xStep;
x += xStep
y += slope*xStep;
this[draw ? 'lineTo' : 'moveTo'](x,y);
distRemaining -= dashLength;
draw = !draw;
}
}
}
To draw a line from 20,150 to 170,10 with dashes that are 30px long followed by a gap of 10px, you would use:
myContext.dashedLine(20,150,170,10,[30,10]);
To draw alternating dashes and dots, use (for example):
myContext.lineCap = 'round';
myContext.lineWidth = 4; // Lines 4px wide, dots of diameter 4
myContext.dashedLine(20,150,170,10,[30,10,0,10]);
The "very short" dash length of 0 combined with the rounded lineCap results in dots along your line.
If anyone knows of a way to access the current point of a canvas context path, I'd love to know about it, as it would allow me to write this as ctx.dashTo(x,y,dashes) instead of requiring you to re-specify the start point in the method call.
This simplified version of Phrogz's code utilises the built-in transformation functionality of Canvas and also handles special cases e.g. when dx = 0
var CP = window.CanvasRenderingContext2D && CanvasRenderingContext2D.prototype;
if (CP.lineTo) {
CP.dashedLine = function(x, y, x2, y2, da) {
if (!da) da = [10,5];
this.save();
var dx = (x2-x), dy = (y2-y);
var len = Math.sqrt(dx*dx + dy*dy);
var rot = Math.atan2(dy, dx);
this.translate(x, y);
this.moveTo(0, 0);
this.rotate(rot);
var dc = da.length;
var di = 0, draw = true;
x = 0;
while (len > x) {
x += da[di++ % dc];
if (x > len) x = len;
draw ? this.lineTo(x, 0): this.moveTo(x, 0);
draw = !draw;
}
this.restore();
}
}
I think my calculations are correct and it seems to render OK.
At the moment at least setLineDash([5,10]) works with Chrome and ctx.mozDash = [5,10] works with FF:
var c=document.getElementById("myCanvas");
var ctx=c.getContext("2d");
if ( ctx.setLineDash !== undefined ) ctx.setLineDash([5,10]);
if ( ctx.mozDash !== undefined ) ctx.mozDash = [5,10];
ctx.beginPath();
ctx.lineWidth="2";
ctx.strokeStyle="green";
ctx.moveTo(0,75);
ctx.lineTo(250,75);
ctx.stroke();
Setting to null makes the line solid.
Phroz's solution is great. But when I used it in my application, I found two bugs.
Following code is debugged (and refactored for readability) version of Phroz's one.
// Fixed: Minus xStep bug (when x2 < x, original code bugs)
// Fixed: Vertical line bug (when abs(x - x2) is zero, original code bugs because of NaN)
var CP = window.CanvasRenderingContext2D && CanvasRenderingContext2D.prototype;
if(CP && CP.lineTo) CP.dashedLine = function(x, y, x2, y2, dashArray){
if(! dashArray) dashArray=[10,5];
var dashCount = dashArray.length;
var dx = (x2 - x);
var dy = (y2 - y);
var xSlope = (Math.abs(dx) > Math.abs(dy));
var slope = (xSlope) ? dy / dx : dx / dy;
this.moveTo(x, y);
var distRemaining = Math.sqrt(dx * dx + dy * dy);
var dashIndex = 0;
while(distRemaining >= 0.1){
var dashLength = Math.min(distRemaining, dashArray[dashIndex % dashCount]);
var step = Math.sqrt(dashLength * dashLength / (1 + slope * slope));
if(xSlope){
if(dx < 0) step = -step;
x += step
y += slope * step;
}else{
if(dy < 0) step = -step;
x += slope * step;
y += step;
}
this[(dashIndex % 2 == 0) ? 'lineTo' : 'moveTo'](x, y);
distRemaining -= dashLength;
dashIndex++;
}
}
Mozilla has been working on an implementation of dashed stroking for canvas, so we may see it added to the spec in the near future.
There's a much simpler way to do this. According to http://www.w3.org/TR/2dcontext/#dom-context-2d-strokestyle strokeStyle accepts strings, CanvasGradients, or CanvasPatterns. So we just take an image like this:
<img src="images/dashedLineProto.jpg" id="cvpattern1" width="32" height="32" />
load it into a canvas, and draw our little rectangle with it.
var img=document.getElementById("cvpattern1");
var pat=ctx.createPattern(img,"repeat");
ctx.strokeStyle = pat;
ctx.strokeRect(20,20,150,100);
that doesnt result in a perfect dashed line, but it's really straightforward and modifiable. Results may of course become imperfect when you're drawing lines which arent horizontal or vertical, a dotted pattern might help there.
PS. keep in mind SOP applies when you're trying to use imgs from external sources in your code.
Looks like context.setLineDash is pretty much implemented.
See this.
" context.setLineDash([5])
will result in a dashed line where both the dashes and spaces are 5 pixels in size. "
There is currently no support in HTML5 Canvas specification for dashed lines.
check this out:
http://davidowens.wordpress.com/2010/09/07/html-5-canvas-and-dashed-lines/
or
Check out the Raphael JS Library:
http://raphaeljs.com/
There are support for it in Firefox at least
ctx.mozDash = [5,10];
seems like ctx.webkitLineDash worked before, but they removed it because it had some compabillity issues.
The W3C specs says ctx.setLineDash([5,10]); but it doesn't seem to be implemented yet anywhere.
I made modified the dashedLine function to add support for offsetting. It utilizes native dashed lines if the browser supports ctx.setLineDash and ctx.lineDashOffset.
Example: http://jsfiddle.net/mLY8Q/6/
var CP = window.CanvasRenderingContext2D && CanvasRenderingContext2D.prototype;
if (CP.lineTo) {
CP.dashedLine = CP.dashedLine || function (x, y, x2, y2, da, offset) {
if (!da) da = [10, 5];
if (!offset) offset = 0;
if (CP.setLineDash && typeof (CP.lineDashOffset) == "number") {
this.save();
this.setLineDash(da);
this.lineDashOffset = offset;
this.moveTo(x, y);
this.lineTo(x2, y2);
this.restore();
return;
}
this.save();
var dx = (x2 - x),
dy = (y2 - y);
var len = Math.sqrt(dx * dx + dy * dy);
var rot = Math.atan2(dy, dx);
this.translate(x, y);
this.moveTo(0, 0);
this.rotate(rot);
var dc = da.length;
var di = 0;
var patternLength = 0;
for (var i = 0; i < dc; i++) {
patternLength += da[i];
}
if (dc % 2 == 1) {
patternLength *= 2;
}
offset = offset % patternLength;
if (offset < 0) {
offset += patternLength;
}
var startPos = 0;
var startSegment = 0;
while (offset >= startPos) {
if (offset >= startPos + da[startSegment % dc]) {
startPos += da[startSegment % dc];
startSegment++;
} else {
offset = Math.abs(offset - startPos);
break;
}
if (startSegment > 100) break;
}
draw = startSegment % 2 === 0;
x = 0;
di = startSegment;
while (len > x) {
var interval = da[di++ % dc];
if (x < offset) {
interval = Math.max(interval - offset, 1);
offset = 0;
}
x += interval;
if (x > len) x = len;
draw ? this.lineTo(x, 0) : this.moveTo(x, 0);
draw = !draw;
}
this.restore();
};
}
I found properties mozDash and mozDashOffset in Mozilla specification:
http://mxr.mozilla.org/mozilla-central/source/dom/interfaces/canvas/nsIDOMCanvasRenderingContext2D.idl
They're probaly used to control dashes, but i haven't used them.

Categories

Resources