I made a project called "pixel paint" by javascript with p5js library, but when I run it, that project ran too slow. I don't know why and how to make it run faster. And here is my code:
let h = 40, w = 64;
let checkbox;
let scl = 10;
let painting = new Array(h);
let brush = [0, 0, 0];
for(let i = 0; i < h; i++) {
painting[i] = new Array(w);
for(let j = 0; j < w; j++) {
painting[i][j] = [255, 255, 255];
}
}
function setup() {
createCanvas(w * scl, h * scl);
checkbox = createCheckbox('Show gird line', true);
checkbox.changed(onChange);
}
function draw() {
background(220);
for(let y = 0; y < h; y++) {
for(let x = 0; x < w; x++) {
fill(painting[y][x]);
rect(x * scl, y * scl, scl, scl);
}
}
if(mouseIsPressed) {
paint();
}
}
function onChange() {
if (checkbox.checked()) {
stroke(0);
} else {
noStroke();
}
}
function paint() {
if(mouseX < w * scl && mouseY < h * scl) {
let x = floor(mouseX / scl);
let y = floor(mouseY / scl);
painting[y][x] = brush;
}
}
<!--Include-->
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.min.js"></script>
Is there a solution to make my project run faster?
The code you have is easy to read.
It might not be worth optimising at this stage as it would make the code potentially needlessly more complex/harder to read and change in the future.
If you want to learn about different ways you could achieve the same thing I can provide a few ideas, though, for your particular use case int terms of performance might won't make a huge difference:
Instead of using the painting as a nested [w][h] array you could use a flat [w * h] array and use a single for loop instead of a nested for loop. This would be somewhat similar to using pixels[]. (You can convert x,y to an index (index = x + (y * width)) and the other way around(x = index % width, y = floor(index / width))
You could in theory use a p5.Image, access pixels[] to draw into it and render using image() (ideally you'd get lower level access to the WebGL renderer to enable antialiasing if it's supported by the browser). The grid itself could be a texture() for a quad where you'd use vertex() to specify not only x,y geometry positions, but also u, v texture coordinates in tandem with textureWrap(REPEAT). (I posted an older repeat Processing example: the logic is the same and syntax is almost identical)
Similar to the p5.Image idea, you can cache the drawing using createGraphics(): e.g. only update the p5.Graphics instance when the mouse is dragged, otherwise render the cached drawing. Additionally you can make use of noLoop()/loop() to control when p5's canvas gets updated (e.g. loop() on mousePressed(), updated graphics on mouseMoved(), noLoop() on mouseReleased())
There are probably other methods too.
While it's good to be aware of techniques to optimise your code,
I strongly recommend not optimising until you need to; and when you do
use a profiler (DevTools has that) to focus only the bits are the slowest
and not waste time and code readability on part of code where optimisation
wouldn't really make an impact.
Related
I'm trying to generate a grid of points on the canvas described by vectors in order to make a flow field. I generate the vectors in a nested loop, then push them to a list, and finally attempt to draw them. However, when I attempt to draw them the .x and .y attributes aren't recognised. I think this is because the list of vectors is empty/only has one entry in it and I can't work out why. Apologies if this is a simple problem - this is my first time using javascript and p5.js. My code is shown below, it should generate a uniform grid of points.
let width = 600;
let height = 600;
let points = [];
function setup() {
createCanvas(width, height);
background(30);
let density = 50;
let spacing = width / density;
for (var x = 0; x < width; x += spacing); {
for (var y = 0; y < height; y += spacing); {
var p = createVector(x, y)
points.push(p)
}
}
}
function draw() {
noStroke();
fill(255);
for (var i = 0; i < points.length; i++); {
circle(points[i].x, points[i].y, 1);
}
}
EDIT: My code is definitely generating one vector, but only one for some reason. So I believe the issue is the for loops not executing correctly.
Your for loop syntax is incorrect. There should not be a semicolon after the closing parenthesis and the opening curly brace:
// !
for (var i = 0; i < points.length; i++); {
circle(points[i].x, points[i].y, 1);
}
You will need to fix each of your for loops.
I am working on a procedural terrain generator, but the 3d Map is constantly morphing and changing, calling for at least 4d noise (5d if I need to make it loop). I haven't found a good perlin/simplex noise library that will work in this many dimensions, so I thought this would be a good time to learn how those algorithms work. After starting to make my own "perlin" noise, I found a large problem. I need to get a psudo random value based on the nD coordinates of that point. So far I have found solutions online that use the dot product of a single point and a vector generated by the inputs, but those became very predictable very fast (I'm not sure why). I then tried a recursive approach (below), and this worked ok, but I got some weird behavior towards the edges.
Recursive 3d randomness attempt:
function Rand(seed = 123456, deg = 1){
let s = seed % 2147483647;
s = s < 1 ? s + 2147483647 : s;
while(deg > 0){
s = s * 16807 % 2147483647;
deg--;
}
return (s - 1) / 2147483646;
}
function DimRand(seed, args){
if(args.length < 2){
return Rand(seed, args[0]);
}else{
let zero = args[0];
args.shift();
return DimRand(Rand(seed, zero), args);
}
}
var T = 1;
var c = document.getElementById('canvas').getContext('2d');
document.getElementById('canvas').height = innerHeight;
document.getElementById('canvas').width = innerWidth;
c.width = innerWidth;
c.height = innerHeight;
var size = 50;
function display(){
for(let i = 0; i < 20; i ++){
for(let j = 0; j < 20; j ++){
var bright = DimRand(89,[i,j])*255
c.fillStyle = `rgb(${bright},${bright},${bright})`
c.fillRect(i*size, j*size, size, size);
}
}
T++;
}
window.onmousedown=()=>{display();}
And here is the result:
The top row was always 1 (White), the 2d row and first column were all 0 (Black), and the 3d row was always very dark (less than ≈ 0.3)
This might just be a bug, or I might have to just deal with it, but I was wondering if there was a better approach.
I am using javascript with p5.js framework. I created an offscreen graphics buffer. Now I want to clear part of that buffer (so it becomes invisible again). What is the best way to do it?
Right now I can only achieve that by direct changing of the alpha value for every pixel I need.
Here is a MCVE:
// sketch.js, requires p5.js
function setup() {
createCanvas(100,100);
background(0);
let mymap = createGraphics(100,100);
mymap.fill(255);
mymap.rect(20,20,40,40);
mymap.loadPixels();
for (let i = 23; i < 37; i++) {
for (let j = 23; j < 37; j++) {
mymap.pixels[400*i+4*j+3] = 0;
}
}
mymap.updatePixels();
image(mymap,0,0);
}
Some side-notes:
1) there is a tiledmap.js library to p5.js, but I am still reading into it's source code and right now it doesn't look like they got 1 buffer for the entire tilemap.
2) there is a clear function to clear the canvas graphics, it does clear things, but it clears everything on the given buffer.
You should be able to use the set() function. More info can be found in the reference.
I would expect something like this to work:
const transparency = createGraphics(20, 20);
mymap.set(30, 30, transparency);
mymap.updatePixels();
Unfortunately, this seems to only set a single pixel. This smells like a bug to me, so I've filed this bug on GitHub.
Like you've discovered, you can also set the pixel values directly. This might be simpler than what you currently have:
let mymap;
function setup() {
createCanvas(100,100);
mymap = createGraphics(100,100);
mymap.fill(255, 0, 0);
mymap.rect(20,20,40,40);
for(let y = 30; y < 50; y++){
for(let x = 30; x < 50; x++){
mymap.set(x, y, color(0, 0, 0, 0));
}
}
mymap.updatePixels();
}
function draw() {
background(0, 255, 0);
image(mymap,0,0);
}
Note that there's a tradeoff: this code is simpler, but it's also slower.
I came across this small JavaScript Program (on Khan Academy) written by someone else:
/*vars*/
frameRate(0);
var Sz=100;
var particles=1000;
scale(400/Sz);
var points=[[floor(Sz/2),floor(Sz/2),false]];
for(var i=0;i<particles;i++){
points.push([floor(random(0,Sz)),floor(random(0,Sz)),true]);
}
var l=points.length-1;
var dirs=[[0,1],[1,0],[0,-1],[-1,0]];
/*functions*/
var move=function(p1){
var mv=dirs[floor(random(0,4))];
var temp=true;
for(var i=l;i>=0;i--){
if(!points[i][2]&&points[i][0]===p1[0]+mv[0]&&points[i][1]===p1[1]+mv[1]){
temp=false;
p1[2]=false;
i=0;
}
}
if(temp){
p1[0]+=mv[0];
p1[1]+=mv[1];
if(p1[0]<0){p1[0]=0;}
if(p1[0]>Sz){p1[0]=Sz;}
if(p1[1]<0){p1[1]=0;}
if(p1[1]>Sz){p1[1]=Sz;}
}
};
/*draw*/
draw= function() {
background(255);
for(var i=points.length-1;i>=0;i--){
stroke(0);
if(points[i][2]){
move(points[i]);
}
else{
stroke(0,0,255);
}
point(points[i][0],points[i][1]);
}
};
I looked at the code and found it a bit difficult to read. So I decided to make my own version with some object orientation:
// apparently, object orientation is a lot slower than just putting the data in arrays
var Point = function(x, y) {
this.x = x;
this.y = y;
this.moving = true;
};
// static constant
Point.dirs = [
{x:0, y:1},
{x:1, y:0},
{x:0, y:-1},
{x:-1, y:0}
];
/*vars*/
frameRate(0);
var Sz=100;
var particles=1000;
scale(400/Sz);
// first point
var points=[new Point(floor(Sz/2), floor(Sz/2))];
points[0].moving = false; // blue
// remaining points
for(var i=0;i<particles;i++){
points.push(new Point(floor(random(0, Sz)), floor(random(0, Sz))));
}
var l=points.length-1;
/*functions*/
var move = function(p1){
var mv = Point.dirs[floor(random(0,4))];
var notAttached = true;
for(var i = l; i >= 0; i--) {
if(!points[i].moving && points[i].x === p1.x + mv.x && points[i].y === p1.y + mv.y) {
notAttached = false;
p1.moving = false;
i = 0;
}
}
if (notAttached) {
p1.x += mv.x;
p1.y += mv.y;
if (p1.x < 0) { p1.x = 0; }
if (p1.x > Sz) { p1.x = Sz; }
if (p1.y < 0) { p1.y = 0; }
if (p1.y > Sz) { p1.y = Sz; }
}
};
/*draw*/
draw= function() {
background(255);
for(var i=points.length-1; i >= 0; i--) {
stroke(0);
if (points[i].moving) {
move(points[i]);
}
else {
stroke(0, 0, 255);
}
point(points[i].x, points[i].y);
}
};
The original just uses arrays for data. Index [0] is an x coordinate, index [1] is a y coordinate, index [2] is a flag.
I think the only changes I made were just what was needed to replace point[0] with point.x, etc.
but I was surprised by how much slower my version was.
Is there a better way to make the code more readable without losing performance? or do we have to lose performance for readability?
JavaScript Engine: Chrome in Windows 10
Edit: more information discovered:
As Ryan pointed out, using plain objects instead of a Point class – new Point(x, y) → {x: x, y: y, moving: false} - improved performance close to the original. So it was just the Point class that made it slow.
So now working with 3 different versions of the program:
array data ( original )
Point class ( 1st rewrite )
plain object ( 2nd rewrite )
In Chrome, the array data and plain object have no easily noticeable difference in performance, the Point class is noticeably slower.
I installed Firefox to test it, and found all three versions to be close to the same performance as each other.
Just eyeballing it, the Firefox speed seems to be in between the slow and the fast speeds I get from Chrome, probably closer to the fast end.
That is why people use bundlers like webpack to make readable code more efficient. Checkout https://webpack.js.org/
For sure you are not the only programmer that sees/will see this code (probably some of the programmers are beginners which it will be hard to even understand the code).
For your code I will choose readability instead of performance!
I would get rid of this and new.
Is this the global object or undefined? I cannot tell from your example! You have to know in which context you are.
Premature optimization is the root of all evil. Donald Knuth.
Browsers have become very good on optimizing the code we wrote.
You can test the speed of your program by using performance.now() which is quite accurate:
var t1 = performance.now()
//your code
var t2 = performance.now()
console.log(t2-t1);
Or you can use jsperf (https://jsperf.com/). I am sure out there are other several websites with this facility.
Great comment from JLRishe: More readable JavaScript code is slower? which I totally agree.
I drawed a grid based system on canvas using PIXI.js.
I'm trying to animate the thing, first each particle position.y is -200, then using Tween.js I'm trying to make them fall.
I change the position to the correct position, which is particle._y.
As you notice you will see after falling there are some empty spaces and CPU is over heating.
http://jsbin.com/wojosopibe/1/edit?html,js,output
function animateParticles() {
for (var k = 0; k < STAGE.children.length; k++) {
var square = STAGE.children[k];
new Tween(square, 'position.y', square._y, Math.floor(Math.random() * 80), true);
}
}
I think I'm doing something wrong.
Can someone please explain me what I'm doing wrong and why there are some empty spaces after falling?
The reason for the empty spaces is that some of your animations are not starting. The cause is in this line:
new Tween(square, 'position.y', square._y, Math.floor(Math.random() * 80), true);
Looking at your function definition for Tween.js, I see this:
function Tween(object, property, value, frames, autostart)
The fourth parameter is frames. I'm assuming this is the number of frames required to complete the animation.
Well your Math.floor function willl sometimes return zero, meaning the animation will have no frames and won't start!!
You can fix this by using math.ceil() instead. This way there will always be at least 1 frame for the animation:
new Tween(square, 'position.y', square._y, Math.ceil(Math.random() * 80), true);
Now, as for performance, I would suggest setting this up differently...
Animating all those graphics objects is very intensive. My suggestion would be to draw a single red square, and then use a RenderTexture to generate a bitmap from the square. Then you can add Sprites to the stage, which perform WAY better when animating.
//Cretae a single graphics object
var g = new PIXI.Graphics();
g.beginFill(0xFF0000).drawRect(0, 0, 2, 2).endFill();
//Render the graphics into a Texture
var renderTexture = new PIXI.RenderTexture(RENDERER, RENDERER.width, RENDERER.height);
renderTexture.render(g);
for (var i = 0; i < CONFIG.rows; i++) {
for (var j = 0; j < CONFIG.cols; j++) {
var x = j * 4;
var y = i * 4;
//Add Sprites to the stage instead of Graphics
var PARTICLE = new PIXI.Sprite(renderTexture);
PARTICLE.x = x;
PARTICLE.y = -200;
PARTICLE._y = H - y;
STAGE.addChild(PARTICLE);
}
}
This link will have some more examples of a RenderTexture:
http://pixijs.github.io/examples/index.html?s=demos&f=render-texture-demo.js&title=RenderTexture