patternjavascriptMinor
JavaScript metaballs optimization
Viewed 0 times
javascriptoptimizationmetaballs
Problem
I created this basic metaball visualization in JavaScript. While it does look nice, it is very slow if you increase number of balls and/or make drawing area larger. I think that the performance is so bad because it iterates through every pixel every time the frame is updated but I cannot think anyway how I could update only those pixels where something has actually happened.
Here's the code:
metaballs
body {
padding: 0px;
margin: 0px;
}
`
Is there anything I can do to optimize this?
Since I'm a total beginner in JavaScript and programming in general, I would be very happy to hear if there are better practices than what I've used here :)
Here's the code:
// canvas
var c = document.getElementById("canvas");
width = c.width = /window.innerWidth;/1280;
height = c.height = /window.innerHeight;/720;
var ctx = c.getContext("2d");
// imagedata
var imageData = ctx.createImageData(width,height);
var data = imageData.data;
// array for balls
var balls = [];
var num = 20;
// random number generation
function random(min, max){
var num = Math.floor(Math.random()*(max-min+1)+min);
return num;
}
// metaball
function ball() {
this.posX = random(0, width);
this.posY = random(0, height);
this.dx = random(-4,4);
this.dy = random(-4,4);
this.radius = random(10, 100);
}
// ball movement
ball.prototype.move = function() {
if (this.posX > width || this.posX height || this.posY metaballs
body {
padding: 0px;
margin: 0px;
}
`
Is there anything I can do to optimize this?
Since I'm a total beginner in JavaScript and programming in general, I would be very happy to hear if there are better practices than what I've used here :)
Solution
JavaScript metaballs optimization
I read that as "JavaScript meatballs optimization" :D
First optimization I would do is... code organization. Slow-looking code will be slow code, since it takes you an extraordinary amount of time working with it. So keep your code tidy. Use proper indentation, name variables properly, organize code in a coherent fashion.
Nested loops become a problem, fast. Avoid nested loops as much as possible. In the case of looping through canvas pixels, instead of using a nested loop to traverse horizontally and vertically, you can simply use one loop and skip by fours. That will give you the same pixel-traversing effect.
But isn't it the same amount of iterations? Yes, but here's the thing. Loops are a dead end in terms of optimization. If you get your x and y that way, that's it. However, if you do the 1d loop and do math to get your x and y, you still have a chance at optimization since math can be optimized (bitwise operations) and values can be cached easily.
Another perf killer is property access. It's negligible alone, but if you're operating at 3M pixels per frame, it becomes very visible. Cache values as much as possible, and avoid property accesses.
One optimization you could do is heuristics. I you know a pixel is far enough from any ball, you can just color it black without calculation. Same goes for pixels within a ball. If it's within a ball, it will be white regardless of overlapping balls. Also, if a pixel is already white, you can just skip calculation of the other balls since there's no way it could darken.
Another trick you could do is just use a smaller canvas and upscale it with CSS
Here's my take on it. Slightly faster due to skipping and upscaling.
``
I read that as "JavaScript meatballs optimization" :D
First optimization I would do is... code organization. Slow-looking code will be slow code, since it takes you an extraordinary amount of time working with it. So keep your code tidy. Use proper indentation, name variables properly, organize code in a coherent fashion.
Nested loops become a problem, fast. Avoid nested loops as much as possible. In the case of looping through canvas pixels, instead of using a nested loop to traverse horizontally and vertically, you can simply use one loop and skip by fours. That will give you the same pixel-traversing effect.
But isn't it the same amount of iterations? Yes, but here's the thing. Loops are a dead end in terms of optimization. If you get your x and y that way, that's it. However, if you do the 1d loop and do math to get your x and y, you still have a chance at optimization since math can be optimized (bitwise operations) and values can be cached easily.
Another perf killer is property access. It's negligible alone, but if you're operating at 3M pixels per frame, it becomes very visible. Cache values as much as possible, and avoid property accesses.
One optimization you could do is heuristics. I you know a pixel is far enough from any ball, you can just color it black without calculation. Same goes for pixels within a ball. If it's within a ball, it will be white regardless of overlapping balls. Also, if a pixel is already white, you can just skip calculation of the other balls since there's no way it could darken.
Another trick you could do is just use a smaller canvas and upscale it with CSS
scale transform. To the script, the number of pixels on the canvas is lesser which means lesser iterations. But from the viewing perspective, the animation is about the same as the full-scale version.Here's my take on it. Slightly faster due to skipping and upscaling.
const c = document.getElementById("canvas");
const width = c.width = 640;
const height = c.height = 360;
const ctx = c.getContext("2d");
const imageData = ctx.createImageData(width, height);
const data = imageData.data;
const dataLength = data.length;
const num = 20;
const balls = Array(num).fill().map(_ => new Ball());
function random(min, max) {
var num = Math.floor(Math.random() * (max - min + 1) + min);
return num;
}
function Ball() {
this.posX = random(0, width);
this.posY = random(0, height);
this.dx = random(-4, 4);
this.dy = random(-4, 4);
this.radius = random(2, 25);
}
Ball.prototype.move = function() {
if (this.posX > width || this.posX height || this.posY = 255) break;
}
data[sp + 0] = color * 1.05;
data[sp + 1] = color;
data[sp + 2] = color * 1.4;
data[sp + 3] = 255;
}
ctx.putImageData(imageData, 0, 0);
}
;(function loop() {
requestAnimationFrame(loop);
updateBalls();
renderBalls();
})();
body {
padding: 0px;
margin: 0px;
}
canvas{
transform: scale(4);
}
``
Context
StackExchange Code Review Q#160012, answer score: 3
Revisions (0)
No revisions yet.