P5.js curveVertex function is closing at a point - javascript

I've created a noise function that pairs with a circle function to create a random noise circle thing that looks pretty cool. My problem is the curveVertex function in P5.js works correctly except for the connection of the first and last vertex. My code is:
let start = Array(50).fill(0); // dont change
let amount = 1; // amount of shapes
let gap = 30; // between shapes
let amplify = 50; // 0 -->
let colorSpeed = 1; // 1 - 9
let colorSeparation = 3; // 0 - 80 recomended 0 - 10
function setup() {
createCanvas(windowWidth, windowHeight);
for(let i = 0 ; i < start.length; i++){
start[i] = random(i);
}
}
function draw() {
background(0);
for(let dnc = (amount + 1) * gap; dnc > gap; dnc -= gap){
drawNoiseCircle(dnc, getNoise(start.length));
}
start = start.map( c => c + 0.01 );
}
function getNoise(amount){
let lengths = [];
for(let i = 1; i < amount + 1; i++){
let n1 = noise(start[i - 1]);
let noise1 = map(n1, 0, 1, -amplify, amplify);
lengths.push(abs(-noise1));
}
return lengths;
}
function drawNoiseCircle(radius, lengths){
colorMode(HSB);
fill(((frameCount + radius) * colorSeparation)/-map(colorSpeed, 1, 10, -10, -1) % 360, 100, 50);
noStroke()
let x;
let y;
beginShape();
for(let l = 0; l < lengths.length; l++){
x = Math.cos(radians(l * 360 / lengths.length)) * (radius + lengths[l]) + width/2;
y = Math.sin(radians(l * 360 / lengths.length)) * (radius + lengths[l]) + height/2;
curveVertex(x, y);
}
endShape(CLOSE);
stroke("black");
line(width/2, height/2, width, height/2);
line(width/2, height/2 + 9, width, height/2 + 9);
}
<script src="https://cdn.jsdelivr.net/npm/p5#1.4.1/lib/p5.js"></script>
I understand the endShape(CLOSED) closes the shape with a straight line, but I'm not sure any other way to close the shape.
You can see the pointed edge on the right side, directly in the middle of the shape.
!EDIT!
I've added lines to the shape to show the line segment that isn't affected by the curve vertex. Also, I understand it may not be a very significant problem, but if the amount of vertexes shrink, it becomes a much bigger problem (eg. a square or a triangle).

Unfortunately I won't have time to dive deep and debug the actual issue with curveVertex (or it's math) at the time, but it seems there's something interesting with curveVertex() in particular.
#Ouoborus point makes sense and the function "should" behave that way (and it was with vertex(), but not curveVertex()). For some reason curveVertex() requires looping over the not just the first point again, but the second and third.
Here's basic example:
function setup() {
createCanvas(300, 300);
background(220);
let numPoints = 6;
let angleIncrement = TWO_PI / numPoints;
let radius = 120;
beginShape();
for(let i = 0 ; i < numPoints + 3; i++){
let angle = angleIncrement * i;
let x = 150 + cos(angle) * radius;
let y = 150 + sin(angle) * radius;
curveVertex(x, y);
}
endShape();
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.1/p5.min.js"></script>
Try decreasing numPoints + 3 to numPoints + 2 or notice the behaviour you're describing.
(I could speculate it might have something to do with how curveVertex() (Catmull-Rom splines) are implemented in p5 and how many coordinates/points it requires, but this isn't accurate without reading the source code and debugging a bit)
Here's a version of your code using the above notes:
let start = Array(30).fill(0);
let colorSpeed = 1; // 1 - 9
let colorSeparation = 3; // 0 - 80 recomended 0 - 10
function setup() {
createCanvas(600, 600);
colorMode(HSB);
noStroke();
// init noise seeds
for(let i = 0 ; i < start.length; i++){
start[i] = random(i);
}
}
function getNoise(seeds, amplify = 50){
let amount = seeds.length;
let lengths = [];
for(let i = 1; i < amount + 1; i++){
let n1 = noise(seeds[i - 1]);
let noise1 = map(n1, 0, 1, -amplify, amplify);
lengths.push(abs(-noise1));
}
return lengths;
}
function drawNoiseCircle(radius, lengths){
let sides = lengths.length;
let ai = TWO_PI / sides;
let cx = width * 0.5;
let cy = height * 0.5;
fill(((frameCount + radius) * colorSeparation)/-map(colorSpeed, 1, 10, -10, -1) % 360, 100, 50);
beginShape();
for(let i = 0 ; i < sides + 3; i++){
let noiseRadius = radius + lengths[i % sides];
let a = ai * i;
let x = cx + cos(a) * noiseRadius;
let y = cy + sin(a) * noiseRadius;
curveVertex(x, y);
}
endShape();
}
function draw() {
background(0);
// draw with updated perlin noise values
drawNoiseCircle(120, getNoise(start));
// increment noise seed
start = start.map( c => c + 0.01 );
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.1/p5.min.js"></script>

Related

Different results with the same code on a computer and a smartphone

Different results are obtained with the same code on a computer(windows 10) and a smartphone(android).I'm working in P5.JS with used loadPixels(). Below is an example code and screenshots. I will also leave a link to OpenProcessing so that you can test the program:
https://openprocessing.org/sketch/1703228
function setup() {
createCanvas(300, 300);
randomSeed(1);
for (let x2=0; x2<width; x2 +=100) {
for (let y2=0; y2<height; y2 += 100) {
fill(random(200),random(55),random(155));
rect(x2,y2,100,100);
}
}
///////
loadPixels();
background(255);
for (let y1=0; y1<height; y1+=100) {
for (let x1=0; x1<width; x1+=100) {
let poz=(x1+y1*width)*4;
let r=pixels[poz];
let g=pixels[poz+1];
let b=pixels[poz+2];
fill(r,g,b);
rect(x1,y1,100,100);
}
}
//////////
}
Computer picture
Smartphone picture
p5js pixels array is different from the original Java's processing pixels array. Very different.
It stores all canvas pixels in 1d array, 4 slots for each pixel:
[pix1R, pix1G, pix1B, pix1A, pix2R, pix2G, pix2B, pix2A...] And also the pixel density mathers.
So your issue is with pixel density that are different from one device to another.
Try differents values to pixelDensity() in the code below. With 1 you get the result that you are getting in PC, with 3 you get the result you get with mobile.
function setup() {
createCanvas(300, 300);
//change here!!
pixelDensity(3);
randomSeed(1);
for (let x2 = 0; x2 < width; x2 += 100) {
for (let y2 = 0; y2 < height; y2 += 100) {
fill(random(200), random(55), random(155));
rect(x2, y2, 100, 100);
}
}
///////
loadPixels();
background(255);
for (let y1 = 0; y1 < height; y1 += 100) {
for (let x1 = 0; x1 < width; x1 += 100) {
let poz = (x1 + y1 * width) * 4;
let r = pixels[poz];
let g = pixels[poz + 1];
let b = pixels[poz + 2];
fill(r, g, b);
rect(x1, y1, 100, 100);
}
}
//////////
}
To make them consistent you need to account for different pixelsDensity in your code.
the following code shows how to account for density using pixels in a determined area, in you case that would be the entire canvas.
To work any given area (a loaded image for instance) you can adapt this snippet:
(here i'm setting the color of the area, but you can get the idea;)
//the area data
const area_x = 35;
const area_y = 48;
const width_of_area = 180;
const height_of_area = 200;
//the pixel density
const d = pixelDensity();
loadPixels();
// those 2 first loops goes trough every pixel in the area
for (let x = area_x; x < width_of_area; x++) {
for (let y = area_y; y < height_of_area; y++) {
//here we go trough the pixels array to get each value of a pixel minding the density.
for (let i = 0; i < d; i++) {
for (let j = 0; j < d; j++) {
// calculate the index of the 1d array for every pixel
// 4 values in the array for each pixel
// y times density times #of pixels
// x idem
index = 4 * ((y * d + j) * width * d + (x * d + i));
// numbers for rgb color
pixels[index] = 255;
pixels[index + 1] = 30;
pixels[index + 2] = 200;
pixels[index + 3] = 255;
}
}
}
}
updatePixels();

Canvas - Create random flakes on image

Can anyone give me a hint how I can create something like that with javascript:
The requirement is that I can set the density of the flakes. and add up to 5 different colors.
I do know how to create a canvas and put pixels in there, but I don't know how to create the "flakes".
Is there a way to create random shapes like this?
You can tessellate a simple shape and draw it at some random point.
The example below will create a 3 sided point, testate it randomly to a detail level of about 2 pixels and then add it to a path.
Then the path is filled with a color and another set of shapes are added.
function testate(amp, points) {
const p = [];
var i = points.length - 2, x1, y1, x2, y2;
p.push(x1 = points[i++]);
p.push(y1 = points[i]);
i = 0;
while (i < points.length) {
x2 = points[i++];
y2 = points[i++];
const dx = x2 - x1;
const dy = y2 - y1;
const r = (Math.random() - 0.5) * 2 * amp;
p.push(x1 + dx / 2 - dy * r);
p.push(y1 + dy / 2 + dx * r);
p.push(x1 = x2);
p.push(y1 = y2);
}
return p;
}
function drawFlake(ctx, size, x, y, noise) {
const a = Math.random() * Math.PI;
var points = [];
const step = Math.PI * (2/3);
var i = 0;
while (i < 3) {
const r = (Math.random() * size + size) / 2;
points.push(Math.cos(a + i * step) * r);
points.push(Math.sin(a + i * step) * r);
i++;
}
while (size > 2) {
points = testate(noise, points);
size >>= 1;
}
i = 0;
ctx.setTransform(1,0,0,1,x,y);
ctx.moveTo(points[i++], points[i++]);
while (i < points.length) {
ctx.lineTo(points[i++], points[i++]);
}
}
function drawRandomFlakes(ctx, count, col, min, max, noise) {
ctx.fillStyle = col;
ctx.beginPath();
while (count-- > 0) {
const x = Math.random() * ctx.canvas.width;
const y = Math.random() * ctx.canvas.height;
const size = min + Math.random() * (max- min);
drawFlake(ctx, size, x, y, noise);
}
ctx.fill();
}
const ctx = canvas.getContext("2d");
canvas.addEventListener("click",drawFlakes);
drawFlakes();
function drawFlakes(){
ctx.setTransform(1,0,0,1,0,0);
ctx.fillStyle = "#341";
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height)
const noise = Math.random() * 0.3 + 0.3;
drawRandomFlakes(ctx, 500, "#572", 5, 10, noise)
drawRandomFlakes(ctx, 200, "#421", 10, 15, noise)
drawRandomFlakes(ctx, 25, "#257", 15, 30, noise)
}
body { background: #341 }
div {
position: absolute;
top: 20px;
left: 20px;
color: white;
}
<canvas id="canvas" width = "600" height = "512"></canvas>
<div>Click to redraw</div>
You'll need a certain noise algorithm.
In this example I used Perlin noise, but you can use any noise algorithm that fits your needs. By using Perlin noise we can define a blob as an area where the noise value is above a certain threshold.
I used a library that I found here and based my code on the sample code. The minified code is just a small portion of it (I cut out simplex and perlin 3D).
LICENSE
You can tweek it by changing the following parameters
Math.abs(noise.perlin2(x / 25, y / 25))
Changing the 25 to a higher value will zoom in, lower will zoom out
if (value > 0.4){
Changing the 0.4 to a lower value will increase blob size, higher will decrease blob size.
!function(n){var t=n.noise={};function e(n,t,e){this.x=n,this.y=t,this.z=e}e.prototype.dot2=function(n,t){return this.x*n+this.y*t},e.prototype.dot3=function(n,t,e){return this.x*n+this.y*t+this.z*e};var r=[new e(1,1,0),new e(-1,1,0),new e(1,-1,0),new e(-1,-1,0),new e(1,0,1),new e(-1,0,1),new e(1,0,-1),new e(-1,0,-1),new e(0,1,1),new e(0,-1,1),new e(0,1,-1),new e(0,-1,-1)],o=[151,160,137,91,90,15,131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23,190,6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33,88,237,149,56,87,174,20,125,136,171,168,68,175,74,165,71,134,139,48,27,166,77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244,102,143,54,65,25,63,161,1,216,80,73,209,76,132,187,208,89,18,169,200,196,135,130,116,188,159,86,164,100,109,198,173,186,3,64,52,217,226,250,124,123,5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42,223,183,170,213,119,248,152,2,44,154,163,70,221,153,101,155,167,43,172,9,129,22,39,253,19,98,108,110,79,113,224,232,178,185,112,104,218,246,97,228,251,34,242,193,238,210,144,12,191,179,162,241,81,51,145,235,249,14,239,107,49,192,214,31,181,199,106,157,184,84,204,176,115,121,50,45,127,4,150,254,138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180],i=new Array(512),w=new Array(512);function u(n){return n*n*n*(n*(6*n-15)+10)}function f(n,t,e){return(1-e)*n+e*t}t.seed=function(n){n>0&&n<1&&(n*=65536),(n=Math.floor(n))<256&&(n|=n<<8);for(var t=0;t<256;t++){var e;e=1&t?o[t]^255&n:o[t]^n>>8&255,i[t]=i[t+256]=e,w[t]=w[t+256]=r[e%12]}},t.seed(0),t.perlin2=function(n,t){var e=Math.floor(n),r=Math.floor(t);n-=e,t-=r;var o=w[(e&=255)+i[r&=255]].dot2(n,t),h=w[e+i[r+1]].dot2(n,t-1),s=w[e+1+i[r]].dot2(n-1,t),a=w[e+1+i[r+1]].dot2(n-1,t-1),c=u(n);return f(f(o,s,c),f(h,a,c),u(t))}}(this);
const c = document.getElementById("canvas");
const cc = c.getContext("2d");
noise.seed(Math.random());
let image = cc.createImageData(canvas.width, canvas.height);
let data = image.data;
for (let x = 0; x < c.width; x++){
for (let y = 0; y < c.height; y++){
const value = Math.abs(noise.perlin2(x / 25, y / 25));
const cell = (x + y * c.width) * 4;
if (value > 0.4){
data[cell] = 256;
data[cell + 1] = 0;
data[cell + 2] = 0;
data[cell + 3] = 256;
}
else {
data[cell] = 0;
data[cell + 1] = 0;
data[cell + 2] = 0;
data[cell + 3] = 0;
}
}
}
cc.putImageData(image, 0, 0);
<canvas id="canvas" width=500 height=500></canvas>

Dynamic Wavy Path/Border

There is something I need to build, but my math ability is not up to par. What I am looking to build is something like this demo, but I need it to be a hybrid of a circle and polygon instead of a line, so to speak. The black line should be dynamic and randomly generated that basically acts as a border on the page.
Currently, I am dissecting this answer with the aim of hopefully being able to transpose it into this, but I am having massive doubts that I will be able to figure this out.
Any idea how to do this or can anybody explain the mathematics?
Below are my notes about the code from the answer I linked above.
var
cw = cvs.width = window.innerWidth,
ch = cvs.height = window.innerHeight,
cx = cw / 2,
cy = ch / 2,
xs = Array(),
ys = Array(),
npts = 20,
amplitude = 87, // can be val from 1 to 100
frequency = -2, // can be val from -10 to 1 in steps of 0.1
ctx.lineWidth = 4
// creates array of coordinates that
// divides page into regular portions
// creates array of weights
for (var i = 0; i < npts; i++) {
xs[i] = (cw/npts)*i
ys[i] = 2.0*(Math.random()-0.5)*amplitude
}
function Draw() {
ctx.clearRect(0, 0, cw, ch);
ctx.beginPath();
for (let x = 0; x < cw; x++) {
y = 0.0
wsum = 0.0
for (let i = -5; i <= 5; i++) {
xx = x; // 0 / 1 / 2 / to value of screen width
// creates sequential sets from [-5 to 5] to [15 to 25]
ii = Math.round(x/xs[1]) + i
// `xx` is a sliding range with the total value equal to client width
// keeps `ii` within range of 0 to 20
if (ii < 0) {
xx += cw
ii += npts
}
if (ii >= npts){
xx -= cw
ii -= npts
}
// selects eleven sequential array items
// which are portions of the screen width and height
// to create staggered inclines in increments of those portions
w = Math.abs(xs[ii] - xx)
// creates irregular arcs
// based on the inclining values
w = Math.pow(w, frequency)
// also creates irregular arcs therefrom
y += w*ys[ii];
// creates sets of inclining values
wsum += w;
}
// provides a relative position or weight
// for each y-coordinate in the total path
y /= wsum;
//y = Math.sin(x * frequency) * amplitude;
ctx.lineTo(x, y+cy);
}
ctx.stroke();
}
Draw();
This is my answer. Please read the comments in the code. I hope this is what you need.
// initiate the canvas
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
let cw = (canvas.width = 600),
cx = cw / 2;
let ch = (canvas.height = 400),
cy = ch / 2;
ctx.fillStyle = "white"
// define the corners of an rectangle
let corners = [[100, 100], [500, 100], [500, 300], [100, 300]];
let amplitud = 20;// oscilation amplitude
let speed = 0.01;// the speed of the oscilation
let points = []; // an array of points to draw the curve
class Point {
constructor(x, y, hv) {
// the point is oscilating around this point (cx,cy)
this.cx = x;
this.cy = y;
// the current angle of oscilation
this.a = Math.random() * 2 * Math.PI;
this.hv = hv;// a variable to know if the oscilation is horizontal or vertical
this.update();
}
// a function to update the value of the angle
update() {
this.a += speed;
if (this.hv == 0) {
this.x = this.cx;
this.y = this.cy + amplitud * Math.cos(this.a);
} else {
this.x = this.cx + amplitud * Math.cos(this.a);
this.y = this.cy;
}
}
}
// a function to divide a line that goes from a to b in n segments
// I'm using the resulting points to create a new point object and push this new point into the points array
function divide(n, a, b) {
for (var i = 0; i <= n; i++) {
let p = {
x: (b[0] - a[0]) * i / n + a[0],
y: (b[1] - a[1]) * i / n + a[1],
hv: b[1] - a[1]
};
points.push(new Point(p.x, p.y, p.hv));
}
}
divide(10, corners[0], corners[1]);points.pop();
divide(5, corners[1], corners[2]);points.pop();
divide(10, corners[2], corners[3]);points.pop();
divide(5, corners[3], corners[0]);points.pop();
// this is a function that takes an array of points and draw a curved line through those points
function drawCurves() {
//find the first midpoint and move to it
let p = {};
p.x = (points[points.length - 1].x + points[0].x) / 2;
p.y = (points[points.length - 1].y + points[0].y) / 2;
ctx.beginPath();
ctx.moveTo(p.x, p.y);
//curve through the rest, stopping at each midpoint
for (var i = 0; i < points.length - 1; i++) {
let mp = {};
mp.x = (points[i].x + points[i + 1].x) / 2;
mp.y = (points[i].y + points[i + 1].y) / 2;
ctx.quadraticCurveTo(points[i].x, points[i].y, mp.x, mp.y);
}
//curve through the last point, back to the first midpoint
ctx.quadraticCurveTo(
points[points.length - 1].x,
points[points.length - 1].y,
p.x,
p.y
);
ctx.stroke();
ctx.fill();
}
function Draw() {
window.requestAnimationFrame(Draw);
ctx.clearRect(0, 0, cw, ch);
points.map(p => {
p.update();
});
drawCurves();
}
Draw();
canvas{border:1px solid; background:#6ab150}
<canvas></canvas>

p5.js - Low FPS for some basic animations

I'm having really bad performance on a project i wrote in Javascript (with the p5.js library)
Here is the code:
const fps = 60;
const _width = 400;
const _height = 300;
const firePixelChance = 1;
const coolingRate = 1;
const heatSourceSize = 10;
const noiseIncrement = 0.02;
const fireColor = [255, 100, 0, 255];
const bufferWidth = _width;
const bufferHeight = _height;
let buffer1;
let buffer2;
let coolingBuffer;
let ystart = 0.0;
function setup() {
createCanvas(_width, _height);
frameRate(fps);
buffer1 = createGraphics(bufferWidth, bufferHeight);
buffer2 = createGraphics(bufferWidth, bufferHeight);
coolingBuffer = createGraphics(bufferWidth, bufferHeight);
}
// Draw a line at the bottom
function heatSource(buffer, rows, _color) {
const start = bufferHeight - rows;
for (let x = 0; x < bufferWidth; x++) {
for (let y = start; y < bufferHeight; y++) {
if(Math.random() >= firePixelChance)
continue;
buffer.pixels[(x + (y * bufferWidth)) * 4] = _color[0]; // Red
buffer.pixels[(x + (y * bufferWidth)) * 4 +1] = _color[1]; // Green
buffer.pixels[(x + (y * bufferWidth)) * 4 +2] = _color[2]; // Blue
buffer.pixels[(x + (y * bufferWidth)) * 4 +3] = 255; // Alpha
}
}
}
// Produces the 'smoke'
function coolingMap(buffer){
let xoff = 0.0;
for(x = 0; x < bufferWidth; x++){
xoff += noiseIncrement;
yoff = ystart;
for(y = 0; y < bufferHeight; y++){
yoff += noiseIncrement;
n = noise(xoff, yoff);
bright = pow(n, 3) * 20;
buffer.pixels[(x + (y * bufferWidth)) * 4] = bright;
buffer.pixels[(x + (y * bufferWidth)) * 4 +1] = bright;
buffer.pixels[(x + (y * bufferWidth)) * 4 +2] = bright;
buffer.pixels[(x + (y * bufferWidth)) * 4 +3] = bright;
}
}
ystart += noiseIncrement;
}
// Change color of a pixel so it looks like its smooth
function smoothing(buffer, _buffer2, _coolingBuffer) {
for (let x = 0; x < bufferWidth; x++) {
for (let y = 0; y < bufferHeight; y++) {
// Get all 4 neighbouring pixels
const left = getColorFromPixelPosition(x+1,y,buffer.pixels);
const right = getColorFromPixelPosition(x-1,y,buffer.pixels);
const bottom = getColorFromPixelPosition(x,y+1,buffer.pixels);
const top = getColorFromPixelPosition(x,y-1,buffer.pixels);
// Set this pixel to the average of those neighbours
let sumRed = left[0] + right[0] + bottom[0] + top[0];
let sumGreen = left[1] + right[1] + bottom[1] + top[1];
let sumBlue = left[2] + right[2] + bottom[2] + top[2];
let sumAlpha = left[3] + right[3] + bottom[3] + top[3];
// "Cool down" color
const coolingMapColor = getColorFromPixelPosition(x,y,_coolingBuffer.pixels)
sumRed = (sumRed / 4) - (Math.random() * coolingRate) - coolingMapColor[0];
sumGreen = (sumGreen / 4) - (Math.random() * coolingRate) - coolingMapColor[1];
sumBlue = (sumBlue / 4) - (Math.random() * coolingRate) - coolingMapColor[2];
sumAlpha = (sumAlpha / 4) - (Math.random() * coolingRate) - coolingMapColor[3];
// Make sure we dont get negative numbers
sumRed = sumRed > 0 ? sumRed : 0;
sumGreen = sumGreen > 0 ? sumGreen : 0;
sumBlue = sumBlue > 0 ? sumBlue : 0;
sumAlpha = sumAlpha > 0 ? sumAlpha : 0;
// Update this pixel
_buffer2.pixels[(x + ((y-1) * bufferWidth)) * 4] = sumRed; // Red
_buffer2.pixels[(x + ((y-1) * bufferWidth)) * 4 +1] = sumGreen; // Green
_buffer2.pixels[(x + ((y-1) * bufferWidth)) * 4 +2] = sumBlue; // Blue
_buffer2.pixels[(x + ((y-1) * bufferWidth)) * 4 +3] = sumAlpha; // Alpha
}
}
}
function draw() {
background(0);
text("FPS: "+Math.floor(frameRate()), 10, 20);
fill(0,255,0,255);
buffer1.loadPixels();
buffer2.loadPixels();
coolingBuffer.loadPixels();
heatSource(buffer1, heatSourceSize, fireColor);
coolingMap(coolingBuffer);
smoothing(buffer1, buffer2, coolingBuffer);
buffer1.updatePixels();
buffer2.updatePixels();
coolingBuffer.updatePixels();
let temp = buffer1;
buffer1 = buffer2;
buffer2 = temp;
image(buffer2, 0, 0); // Draw buffer to screen
// image(coolingBuffer, 0, bufferHeight); // Draw buffer to screen
}
function mousePressed() {
buffer1.fill(fireColor);
buffer1.noStroke();
buffer1.ellipse(mouseX, mouseY, 100, 100);
}
function getColorFromPixelPosition(x, y, pixels) {
let _color = [];
for (let i = 0; i < 4; i++)
_color[i] = pixels[(x + (y * bufferWidth)) * 4 + i];
return _color;
}
function getRandomColorValue() {
return Math.floor(Math.random() * 255);
}
I'm getting ~12 FPS on chrome and ~1 FPS on any other browser and i cant figure out why..
Resizing my canvas to make it bigger also impacts the fps negatively...
In the devtools performance tab i noticed that both my smoothing and coolingMap functions are the things slowing it down, but i cant figure out what part of them are so heavy..
You've pretty much answered this for yourself already:
i'm starting to think this is normal and i should work on caching stuff and maybe use pixel groups instead of single pixels
Like you're discovering, doing some calculation for every single pixel is pretty slow. Computers only have finite resources, and there's going to be a limit to what you can throw at them.
In your case, you might consider drawing the whole thing to a canvas once at startup, and then moving the canvas up over the life of the program.

creating twinkling effect using javascript

var stars = function() {
this.x = Math.floor(Math.random()* 1000) ;
this.y = Math.floor(Math.random()* 900) ;
this.radius = 2 ;
this.starColour = "gold";
}
var starNum = 20;
var starry = new Array(starNum);
for(var s = 0 ; s < 100 ; s++){
starry[s] = new stars()
}
var starDraw = function() {
var starCanvas = document.getElementById("stars");
var starCtx = starCanvas.getContext("2d");
starCtx.clearRect(0, 0, 1000, 900);
for(i = 0; i < 100 ; i++){
var star = starry[i];
starCtx.fillStyle= "white";
starCtx.shadowBlur = 5;
starCtx.shadowColor = "white";
starCtx.beginPath();
// draw it
starCtx.arc(star.x, star.y, star.radius, Math.PI * 2, false);
starCtx.stroke();
starCtx.fill();
}
}
function starLoop(){
starDraw();
requestAnimationFrame(starLoop);
}
requestAnimationFrame(starLoop);
So I am trying to create a twinkling effect for the stars using only javascript and I can't figure out how to do it.
I have searched around and found no real answers up to now so I would appreciate if I could get an answer here. I am very new to coding so please take it easy on me.
A random star field. A little exaggerated, but easy to tone down (or up) if needed.
The important part is to avoid direct random values as most things in nature are not random but tend to fall close to a fixed point. This is call a gaussian distribution. There are several ways to generate such random values.
// gRandom is far more likely to be near 0.5 than 1 or zero
var gRandom = (Math.random()+Math.random()+Math.random()+Math.random()) / 4;
// or
// gRandom is more likely to be near zero than near 1
var gRandom = Math.random() * Math.random();
I use these method to set the sizes of stars (far more small stars than big) and create the colour and movement.
To try and get a more realistic effect I also move the stars by less than a pixel. This has the effect of changing the brightness but not look like movement.
Code has plenty of comments
const ctx = canvas.getContext("2d");
// function calls a callback count times. Saves typing out for loops all the time
const doFor = (count, callback) => {
var i = 0;
while (i < count) {
callback(i++)
}
};
// creates a random integer between min and max. If min only given the between 0 and the value
const randI = (min, max = min + (min = 0)) => (Math.random() * (max - min) + min) | 0;
// same as above but as floats.
const rand = (min, max = min + (min = 0)) => Math.random() * (max - min) + min;
// creates a 2d point at x,y. If only x is a point than set to that point
const point = (x = 0, y) => {
if (x.x && y === undefined) {return { x: x.x,y: x.y} }
return {x,y: y === undefined ? 0 : y }
};
function ease (time, amount = 2) { return Math.pow(time % 1,amount) };
const clamp = (v, min = 1,max = min + (min = 0)) => v < min ? min : v > max ? max : v;
// stuff for stars
const skyColour = [10,30,50];
const density = 1000; // number of star per every density pixels
const colourChangeRate = 16; // Time in frames to change a colour
const stars = [];
const star = { // define a star
draw() {
this.count += 1; // integer counter used to triger color change every 16 frames
if (this.count % colourChangeRate === 0) { // change colour ?
// colour is a gaussian distrabution (NOT random) centered at #888
var c = (Math.random() + Math.random() + Math.random() + Math.random()) * 4;
var str = "#";
str += Math.floor(c * this.red).toString(16); // change color
str += Math.floor(c * this.green).toString(16); // change color
str += Math.floor(c * this.blue).toString(16); // change color
this.col = str;
}
ctx.fillStyle = this.col;
// move star around a pixel. Again its not random
// but a gaussian distrabution. The movement is sub pixel and will only
// make the stars brightness vary not look like its moving
var ox = (Math.random() + Math.random() + Math.random() + Math.random()) / 4;
var oy = (Math.random() + Math.random() + Math.random() + Math.random()) / 4;
ctx.fillRect(this.pos.x + ox, this.pos.y + oy, this.size, this.size);
}
}
// create a random star
// the size is caculated to produce many more smaller stars than big
function createStar(pos) {
stars.push(Object.assign({}, star, {
pos,
col: "#ccc",
count: randI(colourChangeRate),
size: rand(1) * rand(1) * 2 + 0.5,
red: 1-(rand(1) * rand(1) *rand(1)), // reduces colour channels
green: 1-(rand(1) * rand(1) *rand(1)), // but only by a very small amount
blue: 1-(rand(1) * rand(1) *rand(1)), // most of the time but occasional
// star will have a distinct colour
}));
}
var starCount;
var skyGrad;
// render the stars
function mainLoop(time) {
// resize canva if page size changes
if (canvas.width !== innerWidth || canvas.height !== innerHeight) {
canvas.width = innerWidth;
canvas.height = innerHeight;
// create a new set of stars
stars.length = 0;
// density is number of pixels one the canvas that has one star
starCount = Math.floor((canvas.width * canvas.height) / density);
// create the random stars;
doFor(starCount, () => createStar(point(randI(canvas.width), randI(canvas.height))));
skyGrad = ctx.createLinearGradient(0,0,0,canvas.height);
skyGrad.addColorStop(0,"black");
doFor(100,(i)=>{
var pos = clamp(i/100,0,1);
var col = ease(pos);
skyGrad.addColorStop(
pos,
"rgb(" +
Math.floor(skyColour[0] * col) + "," +
Math.floor(skyColour[1] * col) + "," +
Math.floor(skyColour[2] * col) + ")"
);
});
// floating point error can cause problems if we dont set the top
// at 1
skyGrad.addColorStop(1,"rgb("+skyColour[0]+","+skyColour[1]+","+skyColour[2]+")");
}
ctx.fillStyle = skyGrad;
ctx.fillRect(0, 0, canvas.width, canvas.height);
doFor(starCount, (i) => stars[i].draw());
requestAnimationFrame(mainLoop);
}
requestAnimationFrame(mainLoop);
canvas {
position: absolute;
top: 0px;
left: 0px;
}
<canvas id="canvas"></canvas>

Categories

Resources