Filling shape with particles canvas - javascript

Just wondering if anyone could point me in a good direction to a way I could fill an irregular shape with particles, in rows, which would then be animatable.
This is the closest example i can find - http://www.wkams.com/#!/work/detail/coca-cola-music-vis
The two ways I can think would work is work out the density I want, map out how many particles would be needed for each row, and position accordingly. This way seems quite timely and not very robust.
The second way, which I can't seem to figure out how I would do it, is draw the shape in the canvas, then generatively fill the shape with particles, keeping them in the constraints of the shape.
Any general concept of how this could be done would be greatly appreciated.
Let me know if it doesn't make sense.
Cheers

You can use compositing to restrict your particles inside an irregular shape
For each loop of your animation:
Clear the canvas.
Draw your irregular shape on the canvas.
Set compositing to 'source-atop'. This will cause any new drawings to appear only if any newly drawn pixel is over an existing opaque pixel. This is the secret to restricting your particles to be drawn only inside your irregular shape.
Draw your rows of particles. All particles will appear only inside the shape.
Here's example code and a Demo. My example just animates the size of each particle row. You can apply your design requirements to change the size & position of each row.
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
//
ctx.fillStyle='skyblue';
var PI2=Math.PI*2;
//
var w=132;
var h=479;
//
var x1=29;
var x2=177;
var x3=327;
//
var nextTime=0;
var delay=16*2;
var isFading=true;
var isComplete=false;
var opacity=100;
var imgCount=2;
var img=new Image();img.onload=start;img.src="https://dl.dropboxusercontent.com/u/139992952/multple/coke.png";
var label=new Image();label.onload=start;label.src="https://dl.dropboxusercontent.com/u/139992952/multple/label.png";
function start(){
console.log(imgCount);
if(--imgCount>0){return;}
requestAnimationFrame(animate);
$('#again').click(function(){
nextTime=0;
delay=16*2;
opacity=100;
isFading=true;
});
}
function overlay(clipX,x,alpha){
ctx.globalAlpha=alpha;
ctx.drawImage(img,clipX,0,w,h,x,0,w,h);
}
function fillParticles(radius,margin){
var rr=radius*2+margin;
ctx.save();
ctx.clearRect(0,0,cw,ch);
overlay(x3,50,1.00);
ctx.globalCompositeOperation='source-atop';
ctx.beginPath();
var rows=parseInt(ch/(rr))-2;
var cols=parseInt(cw/rr);
for(var r=0;r<rows;r++){
for(var c=0;c<cols;c++){
ctx.arc(c*rr,h-(r*rr),radius,0,PI2);
ctx.closePath();
}}
ctx.fill();
ctx.restore();
overlay(x2,50,1.00);
}
function animate(time){
if(!isComplete){ requestAnimationFrame(animate); }
if(time<nextTime){return;}
if(isFading){
if(--opacity>0){
ctx.clearRect(0,0,cw,ch);
overlay(x1,50,opacity/100);
overlay(x2,50,1.00);
}else{
isFading=false;
overlay(x2,50,1.00);
ctx.drawImage(label,70,210);
nextTime=time+1000;
}
}else{
delay=1000;
fillParticles(parseInt(Math.random()*8)+2,3);
ctx.drawImage(label,70,210);
nextTime=time+delay;
}
}
body{ background-color:white; padding:10px; }
#canvas{border:1px solid red;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<button id=again>Again</button>
<br>
<canvas id="canvas" width=250 height=500></canvas>

If I were to approach this problem, I would go about it in this way:
Create an object that can be used to "create" particles.
Create as many new instances of that object as is needed for the required density.
So, basically, all the work is done by one function constructor/object.
You want this object to provide methods to draw itself to the canvas, store its x and y coordinates, its velocity and direction.
Then you can create instances of this object with the new keyword and set their x and y coordinates to spread them across a grid.

Related

HTML Canvas Hover Text

I have an HTML canvas with all different shapes of all different sizes and it is built by parsing information from an external file. I am wondering how I can make it so that hovering the mouse over each shape will display its unique name. I have found resources on how to display text on a mouse hover for the whole canvas, but I need each individual shape to display a unique text. Thanks!
You can use context.isPointInPath to test if your mouse is hovering over one of your shapes.
Create a javascript object representing each shape from the external file.
var triangle={
name:'triangle',
color:'skyblue',
points:[{x:100,y:100},{x:150,y:150},{x:50,y:150}]
};
Create a function that takes a shape-object and makes a Path from that shape-object:
function defineShape(s){
ctx.beginPath();
ctx.moveTo(s[0].x,s[0].y);
for(var i=1;i<s.length;i++){
ctx.lineTo(s[i].x,s[i].y);
}
ctx.closePath();
}
Use context.isPointInPath to test if the mouse is inside the most recently defined path (from step#2).
// define the path to be tested
defineShape(triangle);
// test if the mouse is inside that shape
if(context.isPointInPath(mouseX,mouseY){
// the mouse is inside the shape
}
Here's example code and a Demo:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
function reOffset(){
var BB=canvas.getBoundingClientRect();
offsetX=BB.left;
offsetY=BB.top;
}
var offsetX,offsetY;
reOffset();
window.onscroll=function(e){ reOffset(); }
window.onresize=function(e){ reOffset(); }
ctx.font='14px verdana';
var shapes=[];
var triangle1={
name:'triangle1',
color:'skyblue',
drawcolor:'skyblue',
points:[{x:100,y:100},{x:150,y:150},{x:50,y:150}]
};
var triangle2={
name:'triangle2',
color:'palegreen',
drawcolor:'palegreen',
points:[{x:220,y:100},{x:270,y:150},{x:170,y:150}]
};
shapes.push(triangle1,triangle2);
$("#canvas").mousemove(function(e){handleMouseMove(e);});
drawAll();
function drawAll(){
for(var i=0;i<shapes.length;i++){
var s=shapes[i];
defineShape(s.points);
ctx.fillStyle=s.drawcolor;
ctx.fill();
ctx.stroke();
if(s.color!==s.drawcolor){
ctx.fillStyle='black';
ctx.fillText(s.name,s.points[0].x,s.points[0].y);
}
}
}
function defineShape(s){
ctx.beginPath();
ctx.moveTo(s[0].x,s[0].y);
for(var i=1;i<s.length;i++){
ctx.lineTo(s[i].x,s[i].y);
}
ctx.closePath();
}
function handleMouseMove(e){
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
// clear the canvas
ctx.clearRect(0,0,cw,ch);
for(var i=0;i<shapes.length;i++){
var s=shapes[i];
// define the shape path we want to test against the mouse position
defineShape(s.points);
// is the mouse insied the defined shape?
if(ctx.isPointInPath(mouseX,mouseY)){
// if yes, fill the shape in red
s.drawcolor='red';
}else{
// if no, fill the shape with blue
s.drawcolor=s.color;
}
}
drawAll();
}
body{ background-color: ivory; }
#canvas{border:1px solid red; margin:0 auto; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<h4>Hover the mouse over the shape.</h4>
<canvas id="canvas" width=300 height=300></canvas>
Are you willing to use a library, or are you attached to a purely native canvas implementation? With purely native canvas, this could get quite annoying, because you would have to not only get the mouse coordinates of the pointer, but also keep track of the position of every object and check whether or not the mouse is at that position every time the mouse moves. I coded that functionality once, and it was annoying enough with just a few rectangles to keep track of.
On the other hand, if you use KineticJS to do your canvas drawing (or presumably others; KineticJS is just the one I've used), all that annoyance is handled for you. The objects you create and add to the canvas can have event handlers attached to them using the KineticJS library, and it will be only minimally harder than reacting to mouseover events on any other HTML element. This link shows how to do it.
http://www.html5canvastutorials.com/kineticjs/html5-canvas-listen-or-dont-listen-to-events-with-kineticjs/

How to delete only a line from the canvas, not all the drawings?

The solution I found is to clear the whole canvas, but I only want to delete one line not all drawings on the canvas.
What should I do?
#danielfranca is right that a line drawn onto the canvas becomes "unremembered pixels in the canvas's sea of pixels."
He's also right that saving snapshot images of the canvas as each line is drawn and reverting to one of those saved images to "delete" a line is resource intensive. (Don't use that technique!!)
But, there is an efficient way to delete previously drawn lines on a canvas!
Yes, it does clear the canvas and redraw the lines, but it's very fast & efficient...I promise!
Here's an outline of how to do it:
Define a line inside an object like this: { x0:10, y0:15, x1:100, y1:75 }
Make as many of those lines as desired and push them into an array: var lines=[];
Use the line definitions in the lines[] array to draw your lines onto the canvas.
Listen for mousemove and mousedown events.
On mousemove, iterate throught lines[] and find the line closest to the mouse. Here's a snippet of the algorithm that calculates which line is closest to a given [mx,my]:
// Find the index of the line closest to mx,my
function setClosestLine(mx,my) {
//
closestLineIndex=-1;
var minDistanceSquared=100000000;
//
// examine each line &
// determine which line is closest to the mouse (mx,my)
for(var i=0;i<lines.length;i++){
var line=lines[i];
var dx=line.x1-line.x0;
var dy=line.y1-line.y0;
var t=((mx-line.x0)*line.dx+(my-line.y0)*line.dy)/line.dx2dy2;
var x=lerp(line.x0, line.x1, t);
var y=lerp(line.y0, line.y1, t);
var dx1=mx-x;
var dy1=my-y;
var distSquared=dx1*dx1+dy1*dy1;
if(distSquared<minDistanceSquared){
minDistanceSquared=distSquared;
closestLineIndex=i;
closestX=x;
closestY=y;
}
}
};
On mousedown, use lines.splice(targetIndex,1) to remove the definition of the closest line from the lines[] array. Then clear the canvas and redraw the remaining lines.
Here's annotated code and a Demo:
// canvas related variables
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
function reOffset(){
var BB=canvas.getBoundingClientRect();
offsetX=BB.left;
offsetY=BB.top;
}
var offsetX,offsetY;
reOffset();
window.onscroll=function(e){ reOffset(); }
ctx.lineWidth=2;
// linear interpolation -- needed in setClosestLine()
var lerp=function(a,b,x){ return(a+x*(b-a)); };
// vars to track which line is closest to the mouse
var closestLineIndex=-1;
var closestX,closestY;
// make some random lines and save them in lines[]
var n=5;
var lines=[];
var randomX=function(){return(Math.random()*cw*.67);}
var randomY=function(){return(Math.random()*ch*.67);}
var lastX=randomX();
var lastY=randomY();
for(var i=0;i<n;i++){
var x=Math.random()*cw*.67;
var y=Math.random()*ch*.67;
var dx=x-lastX;
var dy=y-lastY;
var line={
x0:lastX,
y0:lastY,
x1:x,
y1:y,
weight:Math.round(Math.random()*20),
// precalc often used values
dx:dx,
dy:dy,
dx2dy2:dx*dx+dy*dy,
};
lines.push(line);
lastX=x;
lastY=y;
}
redraw();
$("#canvas").mousedown(function(e){handleMouseDown(e);});
$("#canvas").mousemove(function(e){handleMouseMove(e);});
//////////////////////////////
// functions
// Find the index of the line closest to mx,my
function setClosestLine(mx,my) {
//
closestLineIndex=-1;
var minDistanceSquared=100000000;
//
// examine each line &
// determine which line is closest to the mouse (mx,my)
for(var i=0;i<lines.length;i++){
var line=lines[i];
var dx=line.x1-line.x0;
var dy=line.y1-line.y0;
var t=((mx-line.x0)*line.dx+(my-line.y0)*line.dy)/line.dx2dy2;
var x=lerp(line.x0, line.x1, t);
var y=lerp(line.y0, line.y1, t);
var dx1=mx-x;
var dy1=my-y;
var distSquared=dx1*dx1+dy1*dy1;
if(distSquared<minDistanceSquared){
minDistanceSquared=distSquared;
closestLineIndex=i;
closestX=x;
closestY=y;
}
}
};
// clear & redraw all lines
function redraw(){
// clear the canvas
ctx.clearRect(0,0,cw,ch);
// draw all lines
ctx.strokeStyle='black';
for(var i=0;i<lines.length;i++){
var line=lines[i];
ctx.beginPath();
ctx.moveTo(line.x0,line.y0);
ctx.lineTo(line.x1,line.y1);
ctx.stroke();
}
// draw the line closest to the mouse in red
if(closestLineIndex<0){return;}
var line=lines[closestLineIndex];
ctx.strokeStyle='red';
ctx.beginPath();
ctx.moveTo(line.x0,line.y0);
ctx.lineTo(line.x1,line.y1);
ctx.stroke();
}
// On mousemove, find line closest to mouse
function handleMouseMove(e){
e.preventDefault();
e.stopPropagation();
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
setClosestLine(mouseX,mouseY);
redraw();
}
// On mousedown, remove line that was closest to mouse
function handleMouseDown(e){
e.preventDefault();
e.stopPropagation();
if(closestLineIndex>=0){
lines.splice(closestLineIndex,1);
redraw();
}
}
body {
background-color: ivory;
}
#canvas {
border: 1px solid red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<h4>Closest line to mouse is drawn in red<br>Click to remove that line.</h4>
<canvas id="canvas" width=300 height=300></canvas>
There's no simple way to do that, as the previous information of the pixels are lost after you draw anything.
Here you've a better answer: clear line on HTML5 Canvas
In computer graphics when drawing something, you draw to a buffer. And
when you call lineTo and stroke the buffer is updated and all
information that were in the underlying pixels are lost (or partly
lost if you use transparency) and there is no way to get it back by
undoing (unless there is an implementation containing loads of old
drawings, but that would be really heavy for the memory).
So to be able to undo a stroke might save alot of CPU/GPU time BUT
whould heavily increase the memory
So the only way seems to use clearRect.
Maybe you would give a javascript drawing library a try. There is, for example the oCanvas library, where you draw more object oriented. There is a remove function which removes drawn objects from the canvas.

JS Canvas animate grid elements individually

I'm generating a grid of hexagons by using a for loop and I'm having some issues
for (var i=0; i <= rows; i++) {
for (var j=0; j <= cols; j++) {
ctx.save();
ctx.translate(0+i*distX, 0+j*distY);
drawHexagon(ctx);
ctx.fill();
ctx.restore();
}
}
My end goal is to create a grid of hexagons that move away from the mouse cursor when it's moving around the page, with an area of influence. I can't work out how to draw a path between each of the hexagons and I'm also having an issue with trying to animate the hexagons.
I'm still a canvas newbie, I went through the tutorials on Mozilla's developer network and all of the animations were on singular objects, not objects generated in a grid.
I was thinking that I should try and store the grid and affect it later but I'm not sure how I would go about that, I also don't think canvas works like that.
I found this which is pretty much what I want to do but I can't understand how it works:
http://codepen.io/soulwire/pen/Ffvlo
I'm fine combing through it now, if anyone could walk me through it that would be great :)
Edit: I've since gotten a grid drawn behind the dots, I'd like to manipulate this too. I still don't understand the codepen linked above, it's a little over my head.
Your link applies 2 forces:
Particles near the mouse are repelled. More specifically, if the particles centerpoint is near the mouse centerpoint, then the particle is repelled along the line between the two centerpoints.
Particles not near the mouse are attracted back to their original positions. More specifically, the particles move toward their original centerpoint along the line between their current centerpoint and their original centerpoint.
The math works like this:
// Given the mouse centerpoint (mx,my) & particle's centerpoint (px,py)
// calculate the difference between x's & y's
var dx=px-mx;
var dy=py-my;
// You can repel the particle by increasing the
// particle's position by a fraction of dx & dy
px+=dx/100;
py+=dy/100;
// And you can attract the particle by decreasing the
// particle's position by a fraction of dx & dy
px-=dx/100;
py-=dy/100;
Here's annotated code and a Demo (easing removed for ease of understanding):
// canvas related variables
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
function reOffset(){
var BB=canvas.getBoundingClientRect();
offsetX=BB.left;
offsetY=BB.top;
}
var offsetX,offsetY;
reOffset();
window.onscroll=function(e){ reOffset(); }
window.onresize=function(e){ reOffset(); }
ctx.fillStyle='skyblue';
// mouse related variables
var PI2=Math.PI*2;
var mouseRadius=75; // this is the mouse's radius of influence
var mouseRadiusSquared=mouseRadius*mouseRadius;
var mouseIsDown=false;
var mx,my;
// define a bunch of hex objects stored in an array
var hexRadius=5;
var hexPadding=5;
var hexes=[];
for(var y=hexRadius;y<ch;y+=hexRadius*2+hexPadding){
for(var x=hexRadius;x<cw;x+=hexRadius*2+hexPadding){
hexes.push({startingX:x,startingY:y,x:x,y:y});
}}
// start a continuously running ticker loop
requestAnimationFrame(tick);
// listen for mouse events
$("#canvas").mousedown(function(e){handleMouseDown(e);});
$("#canvas").mouseup(function(e){handleMouseUp(e);});
// draw every hex in its current position
function draw(){
ctx.clearRect(0,0,cw,ch);
ctx.beginPath();
for(var i=0;i<hexes.length;i++){
var h=hexes[i];
ctx.moveTo(h.x,h.y);
ctx.arc(h.x,h.y,hexRadius,0,PI2);
ctx.closePath();
}
ctx.fill();
}
// create a continuously running ticker
function tick(time){
// update each hex position based on its
// position relative to the mouse
for(var i=0;i<hexes.length;i++){
var h=hexes[i];
// calculate if this hex is inside the mouse radius
var dx=h.x-mx;
var dy=h.y-my;
if(mouseIsDown && dx*dx+dy*dy<mouseRadiusSquared){
// hex is inside mouseRadius
// so mouseDown repels hex
h.x+=dx/120;
h.y+=dy/120;
}else if(h.x==h.startingX && h.y==h.startingY){
// hex is at startingX/Y & is not being repelled
// so do nothing
}else{
// hex has moved off startingX/Y
// but is no longer being repelled
// so gravity attracts hex back to its startingX/Y
dx=h.x-h.startingX;
dy=h.y-h.startingY;
h.x-=dx/60;
h.y-=dy/60;
}
}
// redraw the hexes in their new positions
draw();
// request another tick
requestAnimationFrame(tick);
}
// listen for mousedown events
function handleMouseDown(e){
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
// calculate the mouse position
mx=parseInt(e.clientX-offsetX);
my=parseInt(e.clientY-offsetY);
// set the mousedown flag
mouseIsDown=true;
}
// listen for mouseup events
function handleMouseUp(e){
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
// clear the mousedown flag
mouseIsDown=false;
}
body{ background-color: ivory; padding:10px; }
#canvas{border:1px solid red;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<h4>Press the mouse down to repel the particles.<br>Release to return particles to starting point.</h4>
<canvas id="canvas" width=300 height=300></canvas>

How to add 3d perspective to html5 canvas without css3 and webGL

I have this canvas here:
http://jsbin.com/soserubafe
and CODE javascript:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var w = canvas.width;
var h = canvas.height;
var cw=canvas.width;
var ch=canvas.height;
var fov = 250;
var pts = [{x:32,y:59.45},{x:136,y:66},{x:170,y:99},{x:171,y:114},{x:183,y:125},{x:218,y:144},{x:218,y:165},{x:226,y:193},{x:254,y:195},{x:283,y:195},{x:292,y:202},{x:325,y:213},{x:341,y:134},{x:397,y:245},{x:417,y:548}];
mimicSvg(pts);
function mimicSvg(pts){
// make caps & joins round
//ctx.lineCap='round';
ctx.lineJoin='round';
// draw the outside line with red shadow
ctx.shadowColor='red';
ctx.shadowBlur='2';
ctx.lineWidth='40';
ctx.scale(3,3);
// draw multiple times to darken shadow
drawPolyline(pts);
drawPolyline(pts);
drawPolyline(pts);
// stop shadowing
ctx.shadowColor='transparent';
// refill the outside line with pink
ctx.strokeStyle='yellowgreen';
drawPolyline(pts);
// draw the inside line
ctx.lineWidth=2;
ctx.strokeStyle='blue';
drawPolyline(pts);
}
function drawPolyline(pts){
ctx.beginPath();
ctx.moveTo(pts[0].x,pts[0].y);
for(var i=1;i<pts.length;i++){
ctx.lineTo(pts[i].x,pts[i].y);
}
ctx.stroke();
}
and HTML:
<canvas id="canvas" width=1000 height=800></canvas>
How I can add 3d perspective view of this canvas witout using css3, webGL and similar.
Is it possible to add 3d view on 2d canvas?
If you can draw 2d and do math and combine the 2 you can create 3d image (not easy though)
A quick google let me to: http://www.kevs3d.co.uk/dev/phoria/
And this : Possible to run/create 3d animation without need for webgl in HTML5? (so duplicate)

give motion to grass field in canvas

i have created a grass field which is a combination of several small 60x36 images.a grass object is introduced and then drawn on the canvas.now i want to give it motion .the continuous scrolling effect .i made a code for it and it isn't working( the images (the grass field)are not scrolling along the width of the canvas which is the goal of this script).i haven't work much with oop in js. a little discussion on the mistakes i have done will be great
(the image i have used is added to the post)
<html>
<body>
<canvas id="mycanvas"></canvas>
<script>
function makeit(){
var canvas=document.getElementById("mycanvas");
var ctx=canvas.getContext('2d');
var height=500-36;
var xpos=[];
var img=new Image();
img.src="grass.jpg";
drawcanvas();
function drawcanvas(){
canvas.width=600;
canvas.height=500;
canvas.style.border="1px solid black";
}
for(i=0;i<10;i++){
xpos.push(i*60);
}
var grass=function(x,y){
this.x=x;
this.y=y;
this.img=img;
ctx.drawImage(this.img,this.x,this.y);
}
grass.prototype.motion=function(){
for(i=0;i<xpos.length;i++){
xpos[i]--;
if(xpos[i]<=-60){
xpos[i]=canvas.width;
}
ctx.drawImage(this.img,this.x,this.y);
}
}
for(i=0;i<xpos.length;i++){
var grass1=new grass(xpos[i],height);
}
var m=setTimeout(function(){
for(i=0;i<xpos.length;i++){
grass1.motion();
}
},1000);
}
window.onload=makeit;
</script>
</body>
</html>
actual canvas after drawing all the images
In essence, all you need is to create an image pattern then translate and draw it to screen.
An example assuming image has been loaded:
var ph = img.height; // pattern height
var w = canvas.width; // width of canvas/scoll area
var h = canvas.height; // used to calculate y pos.
var x = 0; // scroll position
ctx.fillStyle = ctx.createPattern(img, 'repeat-x'); // pattern
Then in the loop scrolling the grass:
function scroll() {
ctx.translate(x, h - ph); // translate to next position
ctx.fillRect(-x, 0, w, ph); // fill rectangle (fillstyle = pattern)
ctx.translate(-x, -(h -ph)); // translate back for other operations
x--; // scroll speed (here 1 pixel / frame)
requestAnimationFrame(scroll); // loop
}
FIDDLE
Pattern fills are anchored to the coordinate system which is why the translate is necessary. As we translate we also compensate for it using draw position in the opposite direction. This will make the pattern be filled into the same position but at a variable offset which creates the animation effect.
Just note that if you change fillStyle you need to store the pattern in a variable and reinitialize the fill style. If the loop is long-running also limit x so it doesn't overflow. This can be done using w as a condition (or modulo) to reset x to 0.

Categories

Resources