I want to make a box to move as a sinusoidal graph.
At the point where i am now i simply can't represent the box into the canvas. At the beginning I was able to, but after working out the trigonometry part the box disappeared and a get no error...
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<canvas id="canvas" width="600" height="300" style="background-color:red"></canvas>
<script type="text/javascript">
var canvas = document.querySelector("canvas");//isoute me document.getElementsByTagName()
var ctx = canvas.getContext("2d");
var can_width = canvas.width;
var can_height = canvas.height;
var x,y;
function PVector(x_,y_){
var y = [];
var x = [0, Math.PI/6, Math.PI/4, Math.PI/3, Math.PI/2, 2/3*Math.PI, 3/4*Math.PI,
5/6*Math.PI, Math.PI, 7/6*Math.PI, 5/4*Math.PI, 4/3*Math.PI, 3/2*Math.PI,
5/3*Math.PI, 7/4*Math.PI, 11/6*Math.PI, 2*Math.PI];
for (var i=0, len=x["length"]; i<len; i++){
var A;
A = Math.sin(x[i]);
y.push(A);
}console.log(y);console.log(x);
return{
x:x,
y:y
};
}
var Point = {
location : {x:0, y: can_height/2},//initial location
velocity : new PVector(x,y),
display : ctx.fillRect(can_width/2,can_height/2 , 25, 25),//initial position of the box
step : function(){
this.location.x += this.velocity.x;
this.location.y += this.velocity.y;
},
display : function(){
ctx.fillRect(this.location.x, this.location.y, 25, 25);
}
};
function update(){
Point["step"]();
ctx.clearRect(0,0, can_width, can_height);
Point["display"]();
window.setTimeout(update, 1000/30);
}
function init(){
update();
}
init();
</script>
</body>
Problem
In your PVector object you are returning Arrays for x and y, while you use them as values in the step() method. This will cause the entire array to be added as a string.
Solution
You need something that traverse that array. Here is an example, it may not be the result you're after, but it shows the principle which you need to apply:
// first note the return type here:
function PVector(x_,y_){
var y = [];
var x = [0, Math.PI/6, Math.PI/4, Math.PI/3, Math.PI/2, 2/3*Math.PI,
...snipped---
return {
x:x, // x and y are arrays
y:y
};
}
var Point = {
location: {
x: 0,
y: can_height / 2,
step: 0 // some counter to keep track of array position
}, //initial location
velocity: new PVector(x, y),
step: function () {
this.location.step++; // increase step for arrays
// using modulo will keep the step as a valid value within the array length:
// if step = 7 and length = 5, index will become 2 (sort of "wrap around")
var indexX = this.location.step % this.velocity.x.length;
var indexY = this.location.step % this.velocity.y.length
this.location.x += this.velocity.x[indexX];
this.location.y += this.velocity.y[indexY];
},
...
Updated fiddle
Tip: I would as Robin in his answer, recommend to simplify the sinus calculation. Sinus-tables are good when performance is needed and the browser can't keep up (ie. will thousands of objects), but in simpler scenario, direct calculation will work too.
If your goal is just to have a box moving in a sinusoidal graph, it can be done simpler.
This jsfiddle shows a slightly simpler example of a box moving in a sinusoidal graph where I just removed parts of your code and calculate the path with Math.sin and use time instead of precalculated values for x.
function update(){
time += 0.1;
ctx.clearRect(0,0, can_width, can_height);
x = time;
y = (can_height/2)+(can_height/2)*Math.sin(time);
console.log(x, y);
ctx.fillRect(x*16, y, 25, 25);
window.setTimeout(update, 1000/30);
}
The variables are modified to make it look ok on the canvas. You can edit the addition to time, and the altitude and base line for y, to fit your needs.
If you need to follow the specification in your code, look at the answer by Ken.
Since you want a sinusoidal move, it makes sense to use... the sin function.
The formula for a sinusoidal move is :
y = maxValue * sin ( 2 * PI * frequency * time ) ;
where the frequency is in Herz (== 'number of times per second') and time is in second.
Most likely you'll use Date.now(), so you'll have a time in millisecond, that you need to convert. Since the value of PI should not change in the near future, you can compute once the magic number
k = 2 * PI / 1000 = 0.006283185307179587
and the formula becomes :
y = sin( k * frequency * Date.now() );
Here's a simple example on how to use the formula :
var canvas = document.querySelector("canvas");
var ctx = canvas.getContext("2d");
var can_width = canvas.width;
var can_height = canvas.height;
// magic number
var k = 0.006283185307179587;
// oscillations per second
var frequency = 1/5;
// ...
var amplitude = can_width / 8;
function animate() {
ctx.clearRect(0,0,can_width, can_height);
ctx.fillStyle= '#000';
var y = amplitude * Math.sin ( k * frequency * Date.now() );
ctx.fillRect(can_width/2, can_height/2 + y, 20, 20 );
}
setInterval(animate, 30);
<canvas id="canvas" width="400" height="200" style="background-color:red"></canvas>
Related
I am trying to generate a Julia fractal in a canvas in javascript using math.js
Unfortunately every time the fractal is drawn on the canvas, it is rather slow and not very detailed.
Can anyone tell me if there is a specific reason this script is so slow or is it just to much to ask of a browser? (note: the mouse move part is disabled and it is still kinda slow)
I have tried raising and lowering the “bail_num” but everything above 1 makes the browser crash and everything below 0.2 makes everything black.
// Get the canvas and context
var canvas = document.getElementById("myCanvas");
var context = canvas.getContext("2d");
// Width and height of the image
var imagew = canvas.width;
var imageh = canvas.height;
// Image Data (RGBA)
var imagedata = context.createImageData(imagew, imageh);
// Pan and zoom parameters
var offsetx = -imagew/2;
var offsety = -imageh/2;
var panx = -2000;
var pany = -1000;
var zoom = 12000;
// c complexnumber
var c = math.complex(-0.310, 0.353);
// Palette array of 256 colors
var palette = [];
// The maximum number of iterations per pixel
var maxiterations = 200;
var bail_num = 1;
// Initialize the game
function init() {
//onmousemove listener
canvas.addEventListener('mousemove', onmousemove);
// Generate image
generateImage();
// Enter main loop
main(0);
}
// Main loop
function main(tframe) {
// Request animation frames
window.requestAnimationFrame(main);
// Draw the generate image
context.putImageData(imagedata, 0, 0);
}
// Generate the fractal image
function generateImage() {
// Iterate over the pixels
for (var y=0; y<imageh; y++) {
for (var x=0; x<imagew; x++) {
iterate(x, y, maxiterations);
}
}
}
// Calculate the color of a specific pixel
function iterate(x, y, maxiterations) {
// Convert the screen coordinate to a fractal coordinate
var x0 = (x + offsetx + panx) / zoom;
var y0 = (y + offsety + pany) / zoom;
var cn = math.complex(x0, y0);
// Iterate
var iterations = 0;
while (iterations < maxiterations && math.norm(math.complex(cn))< bail_num ) {
cn = math.add( math.sqrt(cn) , c);
iterations++;
}
// Get color based on the number of iterations
var color;
if (iterations == maxiterations) {
color = { r:0, g:0, b:0}; // Black
} else {
var index = Math.floor((iterations / (maxiterations)) * 255);
color = index;
}
// Apply the color
var pixelindex = (y * imagew + x) * 4;
imagedata.data[pixelindex] = color;
imagedata.data[pixelindex+1] = color;
imagedata.data[pixelindex+2] = color;
imagedata.data[pixelindex+3] = 255;
}
function onmousemove(e){
var pos = getMousePos(canvas, e);
//c = math.complex(-0.3+pos.x/imagew, 0.413-pos.y/imageh);
//console.log( 'Mouse position: ' + pos.x/imagew + ',' + pos.y/imageh );
// Generate a new image
generateImage();
}
function getMousePos(canvas, e) {
var rect = canvas.getBoundingClientRect();
return {
x: Math.round((e.clientX - rect.left)/(rect.right - rect.left)*canvas.width),
y: Math.round((e.clientY - rect.top)/(rect.bottom - rect.top)*canvas.height)
};
}
init();
The part of the code that is executed most is this piece:
while (iterations < maxiterations && math.norm(math.complex(cn))< bail_num ) {
cn = math.add( math.sqrt(cn) , c);
iterations++;
}
For the given canvas size and offsets you use, the above while body is executed 19,575,194 times. Therefore there are some obvious ways to improve performance:
somehow reduce the number of points for which the loop must be executed
somehow reduce the number of times these statements are executed per point
somehow improve these statements so they execute faster
The first idea is easy: reduce the canvas dimensions. But this is maybe not something you'd like to do.
The second idea can be achieved by reducing the value for bail_num, because then the while condition will be violated sooner (given that the norm of a complex number is always a positive real number). However, this will just result in more blackness, and gives the same visual effect as zooming out of the center of the fractal. Try for instance with 0.225: there just remains a "distant star". When bail_num is reduced too much, you wont even find the fractal anymore, as everything turns black. So to compensate you would then probably want to change your offset and zoom factors to get a closer view at the center of the fractal (which is still there, BTW!). But towards the center of the fractal, points need more iterations to get below bail_num, so in the end nothing is gained: you'll be back at square one with this method. It's not really a solution.
Another way to work along the second idea is to reduce maxiterations. However, this will reduce the resolution accordingly. It is clear that you will have fewer colors at your disposal, as this number directly corresponds to the number of iterations you can have at the most.
The third idea means that you would somehow optimise the calculations with complex numbers. It turns out to give a lot of gain:
Use efficient calculations
The norm that is calculated in the while condition could be used as an intermediate value for calculating the square root of the same number, which is needed in the next statement. This is the formula for getting the square root from a complex number, if you already have its norm:
__________________
root.re = √ ½(cn.re + norm)
root.im = ½cn.im/root.re
Where the re and im properties denote the real and imaginary components of the respective complex numbers. You can find the background for these formulas in this answer on math.stackexchange.
As in your code the square root is calculated separately, without taking benefit of the previous calculation of the norm, this will certainly bring a benefit.
Also, in the while condition you don't really need the norm (which involves a square root) for comparing with bail_num. You could omit the square root operation and compare with the square of bail_num, which comes down to the same thing. Obviously you would have to calculate the square of bail_num only once at the start of your code. This way you can delay that square root operation for when the condition is found true. The formula for calculating the square of the norm is as follows:
square_norm = cn.re² + cn.im²
The calls of methods on the math object have some overhead, since this library allows different types of arguments in several of its methods. So it would help performance if you would code the calculations directly without relying on math.js. The above improvements already started doing that anyway. In my attempts this also resulted in a considerable gain in performance.
Predefine colours
Although not related to the costly while loop, you can probably gain a litte bit more by calculating all possible colors (per number of iterations) at the start of the code, and store them in an array keyed by number of iterations. That way you can just perform a look-up during the actual calculations.
Some other similar things can be done to save on calculations: For instance, you could avoid translating the screen y coordinate to world coordinates while moving along the X axis, as it will always be the same value.
Here is the code that reduced the original time to complete by a factor of 10, on my PC:
Added intialisation:
// Pre-calculate the square of bail_num:
var bail_num_square = bail_num*bail_num;
// Pre-calculate the colors:
colors = [];
for (var iterations = 0; iterations <= maxiterations; iterations++) {
// Note that I have stored colours in the opposite direction to
// allow for a more efficient "countdown" loop later
colors[iterations] = 255 - Math.floor((iterations / maxiterations) * 255);
}
// Instead of using math for initialising c:
var cx = -0.310;
var cy = 0.353;
Replace functions generateImage and iterate by this one function
// Generate the fractal image
function generateImage() {
// Iterate over the pixels
var pixelindex = 0,
step = 1/zoom,
worldX, worldY,
sq, rootX, rootY, x0, y0;
for (var y=0; y<imageh; y++) {
worldY = (y + offsety + pany)/zoom;
worldX = (offsetx + panx)/zoom;
for (var x=0; x<imagew; x++) {
x0 = worldX;
y0 = worldY;
// For this point: iterate to determine color index
for (var iterations = maxiterations; iterations && (sq = (x0*x0+y0*y0)) < bail_num_square; iterations-- ) {
// root of complex number
rootX = Math.sqrt((x0 + Math.sqrt(sq))/2);
rootY = y0/(2*rootX);
x0 = rootX + cx;
y0 = rootY + cy;
}
// Apply the color
imagedata.data[pixelindex++] =
imagedata.data[pixelindex++] =
imagedata.data[pixelindex++] = colors[iterations];
imagedata.data[pixelindex++] = 255;
worldX += step;
}
}
}
With the above code you don't need to include math.js anymore.
Here is a smaller sized snippet with mouse events handled:
// Get the canvas and context
var canvas = document.getElementById("myCanvas");
var context = canvas.getContext("2d");
// Width and height of the image
var imagew = canvas.width;
var imageh = canvas.height;
// Image Data (RGBA)
var imagedata = context.createImageData(imagew, imageh);
// Pan and zoom parameters
var offsetx = -512
var offsety = -430;
var panx = -2000;
var pany = -1000;
var zoom = 12000;
// Palette array of 256 colors
var palette = [];
// The maximum number of iterations per pixel
var maxiterations = 200;
var bail_num = 0.8; //0.225; //1.15;//0.25;
// Pre-calculate the square of bail_num:
var bail_num_square = bail_num*bail_num;
// Pre-calculate the colors:
colors = [];
for (var iterations = 0; iterations <= maxiterations; iterations++) {
colors[iterations] = 255 - Math.floor((iterations / maxiterations) * 255);
}
// Instead of using math for initialising c:
var cx = -0.310;
var cy = 0.353;
// Initialize the game
function init() {
// onmousemove listener
canvas.addEventListener('mousemove', onmousemove);
// Generate image
generateImage();
// Enter main loop
main(0);
}
// Main loop
function main(tframe) {
// Request animation frames
window.requestAnimationFrame(main);
// Draw the generate image
context.putImageData(imagedata, 0, 0);
}
// Generate the fractal image
function generateImage() {
// Iterate over the pixels
console.log('generate', cx, cy);
var pixelindex = 0,
step = 1/zoom,
worldX, worldY,
sq_norm, rootX, rootY, x0, y0;
for (var y=0; y<imageh; y++) {
worldY = (y + offsety + pany)/zoom;
worldX = (offsetx + panx)/zoom;
for (var x=0; x<imagew; x++) {
x0 = worldX;
y0 = worldY;
// For this point: iterate to determine color index
for (var iterations = maxiterations; iterations && (sq_norm = (x0*x0+y0*y0)) < bail_num_square; iterations-- ) {
// root of complex number
rootX = Math.sqrt((x0 + Math.sqrt(sq_norm))/2);
rootY = y0/(2*rootX);
x0 = rootX + cx;
y0 = rootY + cy;
}
// Apply the color
imagedata.data[pixelindex++] =
imagedata.data[pixelindex++] =
imagedata.data[pixelindex++] = colors[iterations];
imagedata.data[pixelindex++] = 255;
worldX += step;
}
}
console.log(pixelindex);
}
function onmousemove(e){
var pos = getMousePos(canvas, e);
cx = -0.31+pos.x/imagew/150;
cy = 0.35-pos.y/imageh/30;
generateImage();
}
function getMousePos(canvas, e) {
var rect = canvas.getBoundingClientRect();
return {
x: Math.round((e.clientX - rect.left)/(rect.right - rect.left)*canvas.width),
y: Math.round((e.clientY - rect.top)/(rect.bottom - rect.top)*canvas.height)
};
}
init();
<canvas id="myCanvas" width="512" height="200"></canvas>
I am putting together a simulation of an urban transportation system, and trying to improve my Javascript and Canvas skills. I have provided a bare-bones version here: https://jsfiddle.net/ftmzm9vp/
Two questions:
1) I want the "pods" to run at a uniform rate. Right now they are all arriving at their destinations at the same time, which means they are traveling at different speeds. How do I correct this?
2) There is obviously more I have to do -- get the pods to travel along existing lines, work out the best path to their destination, expand the number of lines and stations -- all of which will increase the computing overhead. Right now, with the 500 pods I want to use, the animation is starting to crawl. I rewrote the whole thing to use requestAnimFrame, as I thought it would be faster, but it doesn't seem to be as smooth at it should be. What can I do to improve this?
Thanks!
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<title>Pod Stations Lines Test</title>
<body>
<canvas id="layer1" style="z-index: 2;
position:absolute;
left:0px;
top:0px;
" height="600px" width="1000">This text is displayed if your browser does not support HTML5 Canvas.</canvas>
<canvas id="layer2" style="z-index: 3;
position:absolute;
left:0px;
top:0px;
" height="600px" width="1000">This text is displayed if your browser does not support HTML5 Canvas.</canvas>
<canvas id="layer3" style="z-index: 1;
position:absolute;
left:0px;
top:0px;
" height="600px" width="1000">This text is displayed if your browser does not support HTML5 Canvas.</canvas>
<script>
//Modified Source: http://jsfiddle.net/m1erickson/HAbfm/
//
layer1 = document.getElementById("layer1");
ctx1 = layer1.getContext("2d");
layer2 = document.getElementById("layer2");
ctx2 = layer2.getContext("2d");
layer3 = document.getElementById("layer3");
ctx3 = layer3.getContext("2d");
window.requestAnimFrame = (function(callback) {
return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function(callback) {
window.setTimeout(callback, 1000 / 60);
};
})();
//STATION LIST
var station = [
['A', 100, 50],
['B', 300, 50],
['C', 200, 150],
['D', 100, 250],
['E', 300, 250],
['F', 400, 250]
];
//DRAW LINES
function drawLines() {
ctx1.clearRect(0, 0, layer3.width, layer3.height);
var linkAB = ctx1.beginPath();
ctx1.moveTo(station[0][1], station[0][2]);
ctx1.lineTo(station[1][1], station[1][2]);
ctx1.stroke();
var linkBC = ctx1.beginPath();
ctx1.moveTo(station[1][1], station[1][2]);
ctx1.lineTo(station[2][1], station[2][2]);
ctx1.stroke();
var linkCD = ctx1.beginPath();
ctx1.moveTo(station[2][1], station[2][2]);
ctx1.lineTo(station[3][1], station[3][2]);
ctx1.stroke();
var linkDE = ctx1.beginPath();
ctx1.moveTo(station[3][1], station[3][2]);
ctx1.lineTo(station[4][1], station[4][2]);
ctx1.stroke();
var linkCE = ctx1.beginPath();
ctx1.moveTo(station[2][1], station[2][2]);
ctx1.lineTo(station[4][1], station[4][2]);
ctx1.stroke();
var linkEF = ctx1.beginPath();
ctx1.moveTo(station[4][1], station[4][2]);
ctx1.lineTo(station[5][1], station[5][2]);
ctx1.stroke();
}
//CREATE PODS
var podArray = [];
function Pod(startX, startY, endX, endY, riders, color) {
this.startX = startX;
this.startY = startY;
this.endX = endX;
this.endY = endY;
this.riders = riders;
this.color = color;
}
var colorArray = ["gold", "orange", "red", "green", "blue", "black"];
function randomPass() {
occ = 1 + Math.floor(Math.random() * 6);
return occ;
console.log("Riders " + occ);
}
for (i = 0; i < 500; i++) {
var origNum = Math.floor(Math.random() * station.length);
var origin = {
x: station[origNum][1],
y: station[origNum][2]
}
var destNum = Math.floor(Math.random() * station.length);
while (origNum == destNum) {
destNum = Math.floor(Math.random() * station.length);
}
var destination = {
x: station[destNum][1],
y: station[destNum][2]
}
podArray.push(new Pod(
startX = origin.x,
startY = origin.y,
endX = destination.x,
endY = destination.y,
riders = randomPass(),
color = colorArray[riders - 1]
));
}
var pct = 0.00;
var fps = 60;
//CALL DRAWING AND ANIMATION
drawLines();
animate();
function animate() {
setTimeout(function() {
if (pct <= 1.00) {
requestAnimFrame(animate)
};
// increment the percent (from 0.00 to 1.00)
pct += .01;
// clear the canvas
ctx3.clearRect(0, 0, layer3.width, layer3.height);
// draw all podArray
for (var i = 0; i < podArray.length; i++) {
// get reference to next aPod
var aPod = podArray[i];
var dx = aPod.endX - aPod.startX;
var dy = aPod.endY - aPod.startY;
var nextX = aPod.startX + dx * pct;
var nextY = aPod.startY + dy * pct;
//create pod on screen
ctx3.fillStyle = aPod.color;
ctx3.beginPath();
ctx3.arc(nextX, nextY, 5, 0, Math.PI * 2, true);
ctx3.fillStyle = aPod.color;
ctx3.fill();
ctx3.closePath();
//STATION LETTERS
for (s = 0; s < station.length; s++) {
ctx2.font = '12pt Calibri';
ctx2.fillStyle = 'red';
ctx2.textAlign = 'center';
ctx2.fillText(station[s][0], station[s][1], (station[s][2]) + 4);
}
}
}, 1000 / fps);
}
</script>
</body>
Your vehicles all reach their destination at the same time because you are changing their position based on a percentage. So when pct==1.00 all vehicles arrive simultaneously at their own endpoints regardless of the distance they need to travel to get there.
// increment the percent (from 0.00 to 1.00)
pct += .01;
To make a vehicle arrive based on distance traveled
Question#1: You can calculate each waypoint (waypoint==unique pixel) the vehicle must travel to complete it's route. Advance the vehicle to it's next waypoint with each new animation frame. This causes each vehicle to arrive based on the length of their route rather than a uniform percentage.
Question#2: For each vehicle, if you pre-calculate & save its waypoints into an array, you can easily get 500 vehicles drawn on the canvas during each animation frame.
Here's annotated and a Demo:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
ctx.lineWidth=2;
// define routes
var routes=[];
routes.push({
points:linePoints({x:10,y:10},{x:150,y:10}),
currentPoint:0,
color:'red',
});
routes.push({
points:linePoints({x:10,y:50},{x:250,y:65}),
currentPoint:0,
color:'green',
});
routes.push({
points:linePoints({x:10,y:90},{x:325,y:105}),
currentPoint:0,
color:'blue',
});
// animation related vars
var lastTime=0;
var delay=1000/60*5;
// start animating
requestAnimationFrame(animate);
function animate(time){
// return if the desired time hasn't elapsed
if(time<lastTime){requestAnimationFrame(animate);return;}
// redraw each route
ctx.clearRect(0,0,cw,ch);
// var used to stop animating if all routes are completed
var isComplete=true;
for(var i=0;i<routes.length;i++){
var r=routes[i];
// increase the currentPoint, but not beyond points.length-1
if((r.currentPoint+1)<r.points.length-1){
isComplete=false;
r.currentPoint++;
}
// draw the route to its current point
ctx.strokeStyle=r.color;
ctx.beginPath();
ctx.moveTo(r.points[0].x,r.points[0].y);
ctx.lineTo(r.points[r.currentPoint].x,r.points[r.currentPoint].y);
ctx.stroke();
ctx.fillStyle=r.color;
ctx.beginPath();
ctx.arc(r.points[r.currentPoint].x,r.points[r.currentPoint].y,5,0,Math.PI*2);
ctx.fill();
}
// request another frame unless all routes are completed
if(!isComplete){
requestAnimationFrame(animate);
}
}
function linePoints(p1,p2){
// start building a points array with the starting point
var points=[p1];
var dx=p2.x-p1.x;
var dy=p2.y-p1.y;
var count=Math.sqrt(dx*dx+dy*dy)*3;
for(var pct=0;pct<count;pct++){
// calc next waypoint on the line
var x=p1.x+dx*pct/count;
var y=p1.y+dy*pct/count;
var lastPt=points[points.length-1];
// add new waypoint if the its integer pixel value has
// changed from last waypoint
if(parseInt(x)!==parseInt(lastPt.x) || parseInt(y)!==parseInt(lastPt.y)){
points.push({x:x,y:y});
}
}
// force the last point to be the ending point
points[points.length-1]=p2;
// return a unique points[] forming a line from p1 to p2
return(points);
}
body{ background-color: ivory; }
#canvas{border:1px solid red; margin:0 auto; }
<canvas id="canvas" width=350 height=300></canvas>
To make your pods go the same speed you will probably want to use the Pythagorean theorem. Hypotenuse is the distance you want the node to travel each time the rAF comes around. Then calculate your x's and y's from that.
I'm not quite sure if I understand what pct does.
To speed up the rAF you'll want to:
kill your set timeout
prerender
Prerending is a bit more work but instead of drawing each circle each and every time you have canvases that aren't in the DOM that you draw to. Then you essentially lay the 'hidden' canvas where ever you want on top of the visible DOM canvas. It keeps the drawing in memory this way.
You also draw each circle on the canvas at the end of the for-loop. Pull the fill method outside of it this way the canvas can batch draw instead of a bunch of little calls (this can really kill performance).
Setting font stuff each time can be removed.
Canvas is awesome for performance but you just have to be careful because one small mistake can lead to a huge bottle-neck.
This is a good article: http://www.html5rocks.com/en/tutorials/canvas/performance/
Lemme know if you have any more questions.
Hello and thank you for your help in advance.
I am trying to push/create a new "ring" every couple seconds. I have a ring with a couple variables for the X and Y. The problem I am encountering is, how do I get a new ring and also increment the variables? I need a new variable name for every ring?
Here is how far I have gotten so far:
http://codepen.io/hossman/pen/AfwkF
You can see in the demo how 1 ring goes out, but I want more than 1 ring to go out of my eyes. So for instance 1 ring goes and then it waits a second and then shoots out another ring, so now there are 2 rings on the canvas, then 3, then 4, etc.... I have thought of multiple ways like using arrays and setTimeouts, but I cant put my finger on it. The only other idea I have is to create multiple variables with different names and have each ring be incremented, but thats not very D.R.Y.
Anyhelp?
Please ask questions if I didn't explain it good enough. Thanks again!
Add this to your global vars at the top (and set to whatever you want the distance to be between circles):
var distanceApart = 40;
Then update your main loop like this:
requestAnimationFrame(function print() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
var leftRing = new Ring(x, y);
var rightRing = new Ring(x2, y2);
var temp = startRadius;
var temp2 = 0;
while(temp > 0){
leftRing.draw(ctx, startRadius - temp2 , 'red');
rightRing.draw(ctx, startRadius - temp2 , 'red');
temp2 = temp2 + distanceApart;
temp = temp - distanceApart;
}
startRadius += increase;
requestAnimationFrame(print);
});
Forked here: http://codepen.io/anon/pen/plBmj
(Looks very memorizing!)
I would rewrite parts of your code to enable this. For example I would rewrite your Ring class as follows:
var Ring = defclass({
constructor: function (x, y, r) {
this.x = x;
this.y = y;
this.r = r;
},
draw: function (context) {
context.beginPath();
context.arc(this.x, this.y, this.r, 0, Math.PI * 2);
context.stroke();
return this;
},
addRadius: function (r) {
return new Ring(this.x, this.y, this.r + r);
}
});
Your Ring class constructor now takes x, y and a radius r. The addRadius function returns a new Ring instead of mutating the original one. This is good because immutability makes your code easier to work with. Oh, and defclass is declared as:
function defclass(prototype) {
var constructor = prototype.constructor;
constructor.prototype = prototype;
return constructor;
}
Then we create the two rings for your eyes:
var radius = 10;
var delta = 0.1;
var left = new Ring(cx - (cx / 3.6), cy - 5, radius);
var right = new Ring(cx + (cx / 3.6), cy - 10, radius);
After that we call the animation loop:
var interval = 50 / 3;
var start = Date.now();
loop(start, [left, right]);
Since we want to playback at 60 FPS the interval is 1000 / 60 which can be simplified to 50 / 3. The animation loop is defined as follows:
function loop(last, rings) {
var next = last + interval;
context.clearRect(0, 0, width, height);
var newRings = rings.map(function (ring) {
return ring.draw(context).addRadius(delta);
});
var now = Date.now();
setTimeout(loop, next - now, next,
Math.floor((now - start) / 1000) === rings.length / 2 ?
[left, right].concat(newRings) : newRings);
}
Here's what's happening:
First we clear the screen.
Then we draw all the rings and increase their size.
If one second has elapsed we add two new rings to the array.
Finally we calculate when to call loop again so that it fires after the correct interval.
See the demo: http://jsfiddle.net/LAr76/
I have a black canvas with things being drawn inside it. I want the things drawn inside to fade to black, over time, in the order at which they are drawn (FIFO). This works if I use a canvas which hasn't been resized. When the canvas is resized, the elements fade to an off-white.
Question: Why don't the white specks fade completely to black when the canvas has been resized? How can I get them to fade to black in the same way that they do when I haven't resized the canvas?
Here's some code which demonstrates. http://jsfiddle.net/6VvbQ/35/
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
context.fillRect(0, 0, 300, 150);
// Comment this out and it works as intended, why?
canvas.width = canvas.height = 300;
window.draw = function () {
context.fillStyle = 'rgba(255,255,255,1)';
context.fillRect(
Math.floor(Math.random() * 300),
Math.floor(Math.random() * 150),
2, 2);
context.fillStyle = 'rgba(0,0,0,.02)';
context.fillRect(0, 0, 300, 150);
setTimeout('draw()', 1000 / 20);
}
setTimeout('draw()', 1000 / 20);
The problem is two-parted:
There is a (rather known) rounding error when you draw with low alpha value. The browser will never be able to get the resulting mix of the color and alpha channel equal to 0 as the resulting float value that is mixed will be converted to integer at the time of drawing which means the value will never become lower than 1. Next time it mixes it (value 1, as alpha internally is a value between 0 and 255) will use this value again and it get rounded to again to 1, and forever it goes.
Why it works when you have a resized canvas - in this case it is because you are drawing only half the big canvas to the smaller which result in the pixels being interpolated. As the value is very low this means in this case the pixel will turn "black" (fully transparent) as the average between the surrounding pixels will result in the value being rounded to 0 - sort of the opposite than with #1.
To get around this you will manually have to clear the spec when it is expected to be black. This will involve tracking each particle/spec yourselves or change the alpha using direct pixel manipulation.
Update:
The key is to use tracking. You can do this by creating each spec as a self-updating point which keeps track of alpha and clearing.
Online demo here
A simple spec object can look like this:
function Spec(ctx, speed) {
var me = this;
reset(); /// initialize object
this.update = function() {
ctx.clearRect(me.x, me.y, 1, 1); /// clear previous drawing
this.alpha -= speed; /// update alpha
if (this.alpha <= 0) reset(); /// if black then reset again
/// draw the spec
ctx.fillStyle = 'rgba(255,255,255,' + me.alpha + ')';
ctx.fillRect(me.x, me.y, 1, 1);
}
function reset() {
me.x = (ctx.canvas.width * Math.random())|0; /// random x rounded to int
me.y = (ctx.canvas.height * Math.random())|0; /// random y rounded to int
if (me.alpha) { /// reset alpha
me.alpha = 1.0; /// set to 1 if existed
} else {
me.alpha = Math.random(); /// use random if not
}
}
}
Rounding the x and y to integer values saves us a little when we need to clear the spec as we won't run into sub-pixels. Otherwise you would need to clear the area around the spec as well.
The next step then is to generate a number of points:
/// create 100 specs with random speed
var i = 100, specs = [];
while(i--) {
specs.push(new Spec(ctx, Math.random() * 0.015 + 0.005));
}
Instead of messing with FPS you simply use the speed which can be set individually per spec.
Now it's simply a matter of updating each object in a loop:
function loop() {
/// iterate each object
var i = specs.length - 1;
while(i--) {
specs[i].update(); /// update each object
}
requestAnimationFrame(loop); /// loop synced to monitor
}
As you can see performance is not an issue and there is no residue left. Hope this helps.
I don't know if i have undertand you well but looking at you fiddle i think that, for what you are looking for, you need to provide the size of the canvas in any iteration of the loop. If not then you are just taking the initial values:
EDIT
You can do it if you apply a threshold filter to the canvas. You can run the filter every second only just so the prefromanece is not hit so hard.
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
context.fillRect(0,0,300,150);
//context.globalAlpha=1;
//context.globalCompositeOperation = "source-over";
var canvas2 = document.getElementById('canvas2');
var context2 = canvas2.getContext('2d');
canvas2.width=canvas2.height=canvas.width;
window.draw = function(){
var W = canvas2.width;
var H = canvas2.height;
context2.fillStyle='rgba(255,255,255,1)';
context2.fillRect(
Math.floor(Math.random()*W),
Math.floor(Math.random()*H),
2,2);
context2.fillStyle='rgba(0,0,0,.02)';
context2.fillRect(0,0,W,H);
context.fillStyle='rgba(0,0,0,1)';
context.fillRect(0,0,300,150);
context.drawImage(canvas2,0,0,300,150);
setTimeout('draw()', 1000/20);
}
setTimeout('draw()', 1000/20);
window.thresholdFilter = function () {
var W = canvas2.width;
var H = canvas2.height;
var i, j, threshold = 30, rgb = []
, imgData=context2.getImageData(0,0,W,H), Npixels = imgData.data.length;
for (i = 0; i < Npixels; i += 4) {
rgb[0] = imgData.data[i];
rgb[1] = imgData.data[i+1];
rgb[2] = imgData.data[i+2];
if ( rgb[0] < threshold &&
rgb[1] < threshold &&
rgb[2] < threshold
) {
imgData.data[i] = 0;
imgData.data[i+1] = 0;
imgData.data[i+2] = 0;
}
}
context2.putImageData(imgData,0,0);
};
setInterval("thresholdFilter()", 1000);
Here is the fiddle: http://jsfiddle.net/siliconball/2VaLb/4/
To avoid the rounding problem you could extract the fade effect to a separate function with its own timer, using longer refresh interval and larger alpha value.
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
context.fillRect(0, 0, 300, 150);
// Comment this out and it works as intended, why?
canvas.width = canvas.height = 300;
window.draw = function () {
context.fillStyle = 'rgba(255,255,255,1)';
context.fillRect(
Math.floor(Math.random() * 300),
Math.floor(Math.random() * 300),
2, 2);
setTimeout('draw()', 1000 / 20);
}
window.fadeToBlack = function () {
context.fillStyle = 'rgba(0,0,0,.1)';
context.fillRect(0, 0, 300, 300);
setTimeout('fadeToBlack()', 1000 / 4);
}
draw();
fadeToBlack();
Fiddle demonstrating this: http://jsfiddle.net/6VvbQ/37/
I have a simple Canvas drawing app. Sometimes the lineTo() command produces a line of less coordinates and the drawing has many edges:
I'm using the latest firefox, is it because the connection is bad or my computer is buisy? Is there a work around?
Here is my code: JS FIDDLE
beginPath();
moveTo(this.X, this.Y);
lineTo(e.pageX , e.pageY );
strokeStyle = "rgb(0,0,0)";
ctx.lineWidth=3;
stroke();
It's responding as fast as it can. Your browser will deliver events as fast as it can, but it's not in any way guaranteed to be able to track you moving the mouse. A lot has to do with the load on the client machine.
edit — here is a modified fiddle demonstrating some ways you might make it a little better. That version keeps a separate "points" queue that draws new points every 50 milliseconds. That makes it so that the "mousemove" handler only has to log the point coordinates from the event, and the drawing code can do a bunch of points with one canvas update when the mouse is moving quickly. It's still not perfect.
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var width = window.innerWidth;
var height = window.innerHeight;
canvas.height = height;
canvas.width = width;
canvas.addEventListener('mousedown', function(e) {
this.down = true;
points.setStart(e.pageX, e.pageY);
}, 0);
canvas.addEventListener('mouseup', function() {
this.down = false;
}, 0);
canvas.addEventListener('mousemove', function(e) {
if (this.down) {
points.newPoint(e.pageX, e.pageY);
}
}, 0);
var points = function() {
var queue = [], qi = 0;
var ctx = canvas.getContext('2d');
function clear() {
queue = [];
qi = 0;
}
function setStart(x, y) {
clear();
newPoint(x, y);
}
function newPoint(x, y) {
queue.push([x, y]);
}
function tick() {
var k = 20; // adjust to limit points drawn per cycle
if (queue.length - qi > 1) {
ctx.beginPath();
if (qi === 0)
ctx.moveTo(queue[0][0], queue[0][1]);
else
ctx.moveTo(queue[qi - 1][0], queue[qi - 1][1]);
for (++qi; --k >= 0 && qi < queue.length; ++qi) {
ctx.lineTo(queue[qi][0], queue[qi][1]);
}
ctx.strokeStyle = "rgb(0,0,0)";
ctx.lineWidth = 3;
ctx.stroke();
}
}
setInterval(tick, 50); // adjust cycle time
return {
setStart: setStart,
newPoint: newPoint
};
}();
You can use a cardinal spline to smooth out lines like this:
The cause is as #Pointy already explained due to how fast the browser is able to respond to the events (mousemove). There is an API called Pointer Lock API which might help solve this in the future as it is more low-level, but for now we need to use algorithms to smooth out lines appearing segmented due to this.
In addition to smoothing there is detail-smoothing, point reduction, taper and other things that can be applied to improve the result.
But in this particular case you can use the following function which I made as an extension to the canvas. Just call it:
ctx.curve(myPointArray, tension, segments);
ctx.stroke();
The array contains your x and y points ordered like [x1, y1, x2, y2, ... xn, yn.
A typical value for tension is 0.5. segments (default 16) is optional.
The more tension the more round the curve will appear. Segments are the resolution between each point in the array. For drawing application a value of 5 might work fine (less resulting points).
To make it work better you could register your points on a separate canvas where you draw the original line. At mouse up process the line with this function and draw it to the main canvas and then clear the drawing canvas.
This function is highly optimized - it also returns the processed points so you can store the result instead of re-processing every time.
/**
* curve() by Ken Fyrstenberg (c) 2013 Epistemex
* See Code Project for full source:
* http://www.codeproject.com/Tips/562175/Draw-Smooth-Lines-on-HTML5-Canvas
*/
CanvasRenderingContext2D.prototype.curve = function(pts, ts, nos) {
nos = (typeof numOfSegments === 'undefined') ? 16 : nos;
var _pts = [], res = [], // clone array
x, y, // our x,y coords
t1x, t2x, t1y, t2y, // tension vectors
c1, c2, c3, c4, // cardinal points
st, st2, st3, st23, st32, // steps
t, i, l = pts.length,
pt1, pt2, pt3, pt4;
_pts.push(pts[0]); //copy 1. point and insert at beginning
_pts.push(pts[1]);
_pts = _pts.concat(pts);
_pts.push(pts[l - 2]); //copy last point and append
_pts.push(pts[l - 1]);
this.moveTo(pts[0], pts[1])
for (i = 2; i < l; i+=2) {
pt1 = _pts[i];
pt2 = _pts[i+1];
pt3 = _pts[i+2];
pt4 = _pts[i+3];
// calc tension vectors
t1x = (pt3 - _pts[i-2]) * ts;
t2x = (_pts[i+4] - pt1) * ts;
t1y = (pt4 - _pts[i-1]) * ts;
t2y = (_pts[i+5] - pt2) * ts;
for (t = 0; t <= nos; t++) {
// pre-calc steps
st = t / nos;
st2 = st * st;
st3 = st2 * st;
st23 = st3 * 2;
st32 = st2 * 3;
// calc cardinals
c1 = st23 - st32 + 1;
c2 = st32 - st23;
c3 = st3 - 2 * st2 + st;
c4 = st3 - st2;
res.push(c1 * pt1 + c2 * pt3 + c3 * t1x + c4 * t2x);
res.push(c1 * pt2 + c2 * pt4 + c3 * t1y + c4 * t2y);
} //for t
} //for i
l = res.length;
for(i=0;i<l;i+=2) this.lineTo(res[i], res[i+1]);
return res;
} //func ext
See this answer for an implementation of a cardinal spline.