Related
As stated in the title, rendering slows down significantly at higher resolutions. I'm wondering if this is caused by beginShape() as well as why and how to get around it. Other functions that do not use beginShape() do not affect the frame rate negatively. Link to p5 editor here: https://editor.p5js.org/anton.ermkv/sketches/mSkLrkPJ9
Code below:
function w(v) {if (v == null) return width;return width * v;}
function h(v) {if (v == null) return height;return height * v;}
let zoff = 0;
let irregCircs = []
let numCircs;
function setup() {
createCanvas(windowWidth, windowHeight);
exType = chooseExpandType()
pixelDensity(1)
setIrregCircles(exType)
}
function draw() {
translate(width/2,height/2)
background(255,50);
drawIrregCircles()
console.log(frameRate())
}
function chooseExpandType() {
expandType = 'ellipses'
return expandType
}
function irregExpand(radius,noiseVal) {
beginShape();
for (let a = 0; a <= TWO_PI; a += radians(6)) {
let xoff = map(cos(a), -1, 1, noiseVal/3, noiseVal);
let yoff = map(sin(a), -1, 1, noiseVal/3, noiseVal);
let diff = map(noise(xoff, yoff, zoff), 0, 1, .65, 1.35);
let x = radius * diff * cos(a);
let y = radius * diff * sin(a);
vertex(x,y)
}
endShape(CLOSE);
zoff += 0.0001;
}
function setIrregCircles(expandType) {
numCircs = 4;
for (let i = 0; i < numCircs; i++) {
radius = map(i,0,numCircs,w(.03),w(.65))
noiseVal = random(1,3)
circ = new IrregCircle(radius,noiseVal,expandType,numCircs);
irregCircs.push(circ);
}
}
function drawIrregCircles() {
for (let i = 0; i < irregCircs.length; i++){
irregCircs[i].run();
}
}
class IrregCircle{
constructor(_radius,_noiseVal,_expandType,_numC) {
this.radius = _radius;
this.noiseVal = _noiseVal;
this.expandType = _expandType;
this.numC = _numC;
}
run() {
this.update()
this.checkEdges()
this.show()
}
update() {
this.radius += w(.0015)
}
checkEdges() {
if (this.radius > w(.73)) {
this.radius = w(.01)
}
}
show() {
noFill()
if (this.expandType === 'ellipses'){
push()
rotate(frameCount / 60)
stroke(35,20)
strokeWeight(w(.002))
irregExpand(this.radius,this.noiseVal)
irregExpand(this.radius*1.15,this.noiseVal*1.35)
irregExpand(this.radius*1.3,this.noiseVal*1.7)
irregExpand(this.radius*1.45,this.noiseVal*2)
irregExpand(this.radius*1.6,this.noiseVal*2.3)
irregExpand(this.radius*1.75,this.noiseVal*2.8)
pop()
}
}
}
Thanks in advance to anyone having a look.
the fact that you are printing the frameRate at each frame slows down your program significantly. You can replace:
console.log(frameRate())
by:
if(frameCount % 60 == 0)
console.log(frameRate())
to only print it every 60 frame.
I don't know if it solves your problem but on my side, it seems that it get rid of most of the freezing problem.
As you draw a lot of similar shapes, you should also try to compute an array of the points you need at the beginning and then reuse it at each frame and for each similar shape by scaling it by the right factor (Your code ran a lot faster when I removed the noise to draw circles only so I think what slows your code is the computation inside the BeginShape() block and not the BeginShape() itself).
So I wrote a flood fill function that works like a paint-app bucket tool: you click inside a closed shape and it'll fill with a color.
I have two problems with it:
performance - let's say my canvas is 600*600 (370,000 pixels) and I draw a big circle in it that for example has about 100K pixels in it, it can take about 40(!!!) seconds to fill this circle! thats insane!
A sqaure of exactly 10,000 pixels takes 0.4-0.5 seconds on average, but (I guess) since the sizes of the arrays used the program are growing so much, a sqaure 10 times the size takes about 100 times the length to fill.
there's something wierd about the filling. I'm not really sure how it happens but it's always leaving a few un-filled pixels. Not much at all, but it's really wierd.
My flood fill function uses 4 helper-functions: get and set pixel color, checking if it's a color to fill, and checking if that's a pixel that has been checked before.
Here are all the functions:
getPixelColor = (x, y) => {
let pixelColor = [];
for (let i = 0; i < pixDens; ++i) {
for (let j = 0; j < pixDens; ++j) {
index = 4 * ((y * pixDens + j) * width * pixDens + (x * pixDens + i));
pixelColor[0] = pixels[index];
pixelColor[1] = pixels[index + 1];
pixelColor[2] = pixels[index + 2];
pixelColor[3] = pixels[index + 3];
}
}
return pixelColor;
};
setPixelColor = (x, y, currentColor) => { //Remember to loadPixels() before using this function, and to updatePixels() after.
for (let i = 0; i < pixDens; ++i) {
for (let j = 0; j < pixDens; ++j) {
index = 4 * ((y * pixDens + j) * width * pixDens + (x * pixDens + i));
pixels[index] = currentColor[0];
pixels[index + 1] = currentColor[1];
pixels[index + 2] = currentColor[2];
pixels[index + 3] = currentColor[3];
}
}
}
isDuplicate = (posHistory, vector) => {
for (let i = 0; i < posHistory.length; ++i) {
if (posHistory[i].x === vector.x && posHistory[i].y === vector.y) {
return true;
}
}
return false;
}
compareColors = (firstColor, secondColor) => {
for (let i = 0; i < firstColor.length; ++i) {
if (firstColor[i] !== secondColor[i]) {
return false;
}
}
return true;
}
floodFill = () => {
loadPixels();
let x = floor(mouseX);
let y = floor(mouseY);
let startingColor = getPixelColor(x, y);
if (compareColors(startingColor, currentColor)) {
return false;
}
let pos = [];
pos.push(createVector(x, y));
let posHistory = [];
posHistory.push(createVector(x, y));
while (pos.length > 0) {
x = pos[0].x;
y = pos[0].y;
pos.shift();
if (x <= width && x >= 0 && y <= height && y >= 0) {
setPixelColor(x, y, currentColor);
let xMinus = createVector(x - 1, y);
if (!isDuplicate(posHistory, xMinus) && compareColors(getPixelColor(xMinus.x, xMinus.y), startingColor)) {
pos.push(xMinus);
posHistory.push(xMinus);
}
let xPlus = createVector(x + 1, y);
if (!isDuplicate(posHistory, xPlus) && compareColors(getPixelColor(xPlus.x, xPlus.y), startingColor)) {
pos.push(xPlus);
posHistory.push(xPlus);
}
let yMinus = createVector(x, y - 1);
if (!isDuplicate(posHistory, yMinus) && compareColors(getPixelColor(yMinus.x, yMinus.y), startingColor)) {
pos.push(yMinus);
posHistory.push(yMinus);
}
let yPlus = createVector(x, y + 1);
if (!isDuplicate(posHistory, yPlus) && compareColors(getPixelColor(yPlus.x, yPlus.y), startingColor)) {
pos.push(yPlus);
posHistory.push(yPlus);
}
}
}
updatePixels();
}
I would really apprciate it if someone could help me solve the problems with the functions.
Thank you very much!!
EDIT: So I updated my flood fill function itself and removed an array of colors that I never used. this array was pretty large and a few push() and a shift() methods called on it on pretty much every run.
UNFORTUNATLY, the execution time is 99.9% the same for small shapes (for example, a fill of 10,000 takes the same 0.5 seconds, but large fills, like 100,000 pixels now takes about 30 seconds and not 40, so that's a step in the right direction.
I guess that RAM usage is down as well since it was a pretty large array but I didn't measured it.
The wierd problem where it leaves un-filled pixels behind is still here as well.
A little suggestion:
You don't actually have to use the posHistory array to determine whether to set color. If the current pixel has the same color as startingColor then set color, otherwise don't set. This would have the same effect.
The posHistory array would grow larger and larger during execution. As a result, a lot of work has to be done just to determine whether to fill a single pixel. I think this might be the reason behind your code running slowly.
As for the "weird thing":
This also happened to me before. I think that's because the unfilled pixels do not have the same color as startingColor. Say you draw a black shape on a white background, you would expect to see some gray pixels (close to white) between the black and white parts somewhere. These pixels play the role of smoothing the shape.
With the following code, I'm looping through an array of colors (favorites), creating rectangles for a jsPDF document.
After 5 iterations, I want to reset the x variable back to startX and then add 1.875 with each iteration. Likewise for the next 5 iterations: reset x to startX adding 1.875 until 10, then again until 15.
I'm just not having any luck resetting x in these conditionals. I'm sure it's something obvious but what am I missing here?
Or should I structure the loop in a different way?
What I'm trying to accomplish is create up to 3 rows of 5 rectangles. Once I hit 5, start a new row, hence the reset of x which is a page location coordinate.
let startX = 1
let startY = 1
let secondY = 4
let thirdY = 6.5
let n = favorites.length
for (let i = 0, x = startX, y = startY; i < n; x += 1.875, i++) {
if (i < 5) {
doc.setFillColor(favorites[i].h)
doc.rect(x, y, 1.5, 1, 'F')
doc.text(favorites[i].h.toString(), x, y + 1.5)
} else if (i >= 5 && i < 10) {
x = 1 // resets but then doesn't increment
y = secondY
doc.setFillColor(favorites[i].h)
doc.rect(x, y, 1.5, 1, 'F')
doc.text(favorites[i].h.toString(), x, y + 1.5)
} else if (i >= 10 && i < 15) {
x = 1 // resets but then doesn't increment
y = thirdY
doc.setFillColor(favorites[i].h)
doc.rect(x, y, 1.5, 1, 'F')
doc.text(favorites[i].h.toString(), x, y + 1.5)
}
}
You can use the modulo operator (%), and set x and y outside the loop declaration:
const yValues = [1, 4, 6.5];
for (let i = 0 ; i < 15; i++) {
const x = 1 + ((i%5) * 1.875);
const y = yValues[Math.floor(i/5)];
// commented lines to make this example run
// doc.setFillColor(favorites[i].h)
// doc.rect(x, y, 1.5, 1, 'F')
// doc.text(favorites[i].h.toString(), x, y + 1.5)
console.log({x,y});
}
Incrementation in a for loop occur before any commands in the loop. Right now, every iteration in your second and third if blocks resets x to 1, and always does so after x's incrementation in the for loop, thus overwriting it. That's why x isn't changing.
A better approach might be to increment only i, and set x to depend on i's value, something like this:
x = 1 + ((i - 5) * 1.875)
x = 1 + ((i - 10) * 1.875)
And actually, it would be even better to use startX instead of 1:
x = startX + ((i - 5) * 1.875)
x = startX + ((i - 10) * 1.875)
Edited : Thanks to all for valuable time and effort. Finally I made this )) JSfiddle
I was just playing with canvas and made this. Fiddle link here.
... some code here ...
var cords = [];
for(var i = 50; i <= width; i += 100) {
for(var j = 50; j <= height; j += 100) {
cords.push({ cor: i+','+j});
}
}
console.log(cords);
var offset = 15,
speed = 0.01,
angle = 0.01;
cords.forEach(function(e1) {
e1.base = parseInt(Math.random()*25);
e1.rgb = 'rgb('+parseInt(Math.random()*255)+','+parseInt(Math.random()*255)+','+parseInt(Math.random()*255)+')';
});
setInterval(function() {
cords.forEach(function(e1) {
e1.base = parseInt(Math.random()*25);
e1.rgb = 'rgb('+parseInt(Math.random()*255)+','+parseInt(Math.random()*255)+','+parseInt(Math.random()*255)+')';
});
},5000);
function render() {
ctx.clearRect(0,0,width,height);
cords.forEach(function(e1) {
//console.log(e1);
ctx.fillStyle = e1.rgb;
ctx.beginPath();
var r = e1.base + Math.abs(Math.sin(angle)) * offset;
var v = e1.cor.split(',');
ctx.arc(v[0],v[1],r,0,Math.PI * 2, false);
ctx.fill();
});
angle += speed;
requestAnimationFrame(render);
}
render();
Was wondering if -
Coordinates can be made random, now they are fixed as you can see. After 5000 mil, balls will show up in various random cords but even at their fullest they won't touch each other.
Every ball has same speed for changing size, I want that to be different too. Meaning, After 5000 mil, they show up with different animation speeds as well.
Also any suggestion on improving code and making it better/quicker/lighter is much appreciated. Thank you !
TL;DR - See it running here.
Making the coordinates random:
This requires you to add some random displacement to the x and y coordinates. So I added a random value to the coordinates. But then a displacement of less than 1 is not noticeable. So you'd need to magnify that random number by a multiplier. That's where the randomizationFactor comes in. I have set it to 100 since that is the value by which you shift the coordinates in each iteration. So that gives a truly random look to the animation.
Making Speed Random:
This one took me a while to figure out, but the ideal way is to push a value of speed into the array of coordinates. This let's you ensure that for the duration of animation, the speed will remain constant and that gives you a smoother feel. But again multiplying the radius r with a value between 0 and 1 reduces the speed significantly for some of the circles. So I have added a multiplier to 3 to compensate slightly for that.
Ideally I'd put a 2, as the average value of Math.random() is 0.5, so a multiplier of 2 would be adequate to compensate for that. But a little experimentation showed that the multiplier of 3 was much better. You can choose the value as per your preference.
Your logic of generating the coordinates changes as follows:
for(var i = 50; i <= width;i += 100) {
for(var j = 51; j <= height;j += 100) {
var x = i + (Math.random() - 0.5)*randomizationFactor;
var y = j + (Math.random() - 0.5)*randomizationFactor;
cords.push({ cor: x+','+y, speed: Math.random()});
}
}
Your logic of enlarging the circles changes as follows:
function render() {
ctx.clearRect(0,0,width,height);
cords.forEach(function(e1) {
//console.log(e1);
ctx.fillStyle = e1.rgb;
ctx.beginPath();
var r = e1.base + Math.abs(Math.sin(angle)) * offset * e1.speed * 3;
var v = e1.cor.split(',');
ctx.arc(v[0],v[1],r,0,Math.PI * 2, false);
ctx.fill();
});
angle += speed ;
requestAnimationFrame(render);
}
Suggestion: Update the coordinates with color
I'd probably also update the location of circles every 5 seconds along with the colors. It's pretty simple to do as well. Here I've just created a function resetCoordinates that runs every 5 seconds along with the setBaseRgb function.
var cords = [];
function resetCoordinates() {
cords = [];
for(var i = 50; i <= width;i += 100) {
for(var j = 51; j <= height;j += 100) {
var x = i + (Math.random() - 0.5)*randomizationFactor;
var y = j + (Math.random() - 0.5)*randomizationFactor;
cords.push({ cor: x+','+y, speed: Math.random()});
}
}
}
UPDATE I did some fixes in your code that can make your animation more dynamic. Totally rewritten sample.
(sorry for variable name changing, imo now better)
Built in Math.random not really random, and becomes obvious when you meet animations. Try to use this random-js lib.
var randEngine = Random.engines.mt19937().autoSeed();
var rand = function(from, to){
return Random.integer(from, to)(randEngine)
}
Internal base properties to each circle would be better(more dynamic).
var circles = [];
// better to save coords as object neither as string
for(var i = 50; i <= width; i += 100)
for(var j = 50; j <= height; j += 100)
circles.push({
coords: {x:i,y:j}
});
We can adjust animation with new bouncing property.
var offset = 15,
speed = 0.005,
angle = 0.01,
bouncing = 25;
This is how setBaseRgb function may look like
function setBaseRgb(el){
el.base = rand(-bouncing, bouncing);
el.speed = rand(5, 10) * speed;
el.angle = 0;
el.rgb = 'rgb('+rand(0, 255)+','+rand(0, 255)+','+rand(0, 255)+')';
}
All your animations had fixed setInterval timeout. Better with random timeout.
cords.forEach(function(el){
// random timeout for each circle
setInterval(setBaseRgb.bind(null,el), rand(3000, 5000));
})
You forgot to add your base to your circle position
function render() {
ctx.clearRect(0,0,width,height);
circles.forEach(function(el) {
ctx.fillStyle = el.rgb;
ctx.beginPath();
var r = bouncing + el.base + Math.abs(Math.sin(el.angle)) * offset;
var coords = el.coords;
ctx.arc(
coords.x + el.base,
coords.y + el.base,
r, 0, Math.PI * 2, false
);
ctx.fill();
el.angle += el.speed;
});
requestAnimationFrame(render);
}
render();
Effect 1 JSFiddle
Adding this
if(el.angle > 1)
el.angle=0;
Results bubling effect
Effect 2 JSFiddle
Playing with formulas results this
Effect 3 JSFiddle
I'm splitting a element into multiple blocks (defined by a number of rows and columns), and then fade these blocks to create animation effects. The type of animation is decided by the delay() value:
$('.block').each(function (i) {
$(this).stop().delay(30 * i).animate({
'opacity': 1
}, {
duration: 420
});
});
In this case each block's fade effect is delayed by (30 * current block index). The first block gets 0 delay, the second block 30 delay, ..... the last block 30 * (number of blocks) delay. So this will fade all blocks horizontally.
I've posted a list of effects I've come up so far here: http://jsfiddle.net/MRPDw/.
What I need help with is to find the delay expression for a spiral type effect, and maybe others that you think are possible :D
Here is an example of code for a spiral pattern:
case 'spiral':
$('.block', grid).css({
'opacity': 0
});
var order = new Array();
var rows2 = rows/2, x, y, z, n=0;
for (z = 0; z < rows2; z++){
y = z;
for (x = z; x < cols - z - 1; x++) {
order[n++] = y * cols + x;
}
x = cols - z - 1;
for (y = z; y < rows - z - 1; y++) {
order[n++] = y * cols + x;
}
y = rows - z - 1;
for (x = cols - z - 1; x > z; x--) {
order[n++] = y * cols + x;
}
x = z;
for (y = rows - z - 1; y > z; y--) {
order[n++] = y * cols + x;
}
}
for (var m = 0; m < n; m++) {
$('.block-' + order[m], grid).stop().delay(100*m).animate({
opacity: 1
}, {
duration: 420,
complete: (m != n - 1) ||
function () {
alert('done');
}
});
}
break;
See it working in this fiddle.
I also improved on your "RANDOM" animation, to show all the squares, not just a subset. The code for that is:
case 'random':
var order = new Array();
var numbers = new Array();
var x, y, n=0, m=0, ncells = rows*cols;
for (y = 0; y < rows; y++){
for (x = 0; x < cols; x++){
numbers[n] = n++;
}
}
while(m < ncells){
n = Math.floor(Math.random()*ncells);
if (numbers[n] != -1){
order[m++] = n;
numbers[n] = -1;
}
}
$('.block', grid).css({
'opacity': 0
});
for (var m = 0; m < ncells; m++) {
$('.block-' + order[m], grid).stop().delay(100*m).animate({
opacity: 1
}, {
duration: 420,
complete: (m != ncells - 1) ||
function () {
alert('done');
}
});
}
break;
See it working in this fiddle.
Maybe the easiest way to think about making a spiral animation, is to think about your matrix as a piece of paper.
If you fold 2 times that paper in the x and y center axes, you end up getting a smaller square (or rectangle) quadrant.
Now, if you animate this quadrant only from bottom right to top left corner (in the same way you did for your 'diagonal-reverse'), you can propagate this movement to the other 3 quadrants in order to get the final effect of having an animation running from the center of your matrix up to the four corners.
case 'spiral':
$('.block', grid).css({
'opacity': 0
});
n = 0;
var center = {
x: cols / 2,
y: rows / 2
};
// iterate on the second quadrant only
for (var y = 0; y < center.y; y++)
for (var x = 0; x < center.x; x++) {
// and apply the animation to all quadrants, by using the multiple jQuery selector
$('.block-' + (y * rows + x) + ', ' + // 2nd quadrant
'.block-' + (y * rows + cols - x - 1) + ', ' + // 1st quadrant
'.block-' + ((rows - y - 1) * rows + x) + ', ' + // 3rd quadrant
'.block-' + ((rows - y - 1) * rows + cols - x - 1) // 4th quadrant
, grid).stop().delay(100 * (center.y - y + center.x - x)).animate({
opacity: 1
}, {
duration: 420,
complete: function () {
if (++n == rows * cols) {
alert('done'); // fire next animation...
}
}
});
}
Here is the demo (click the spiral link)