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.
Related
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.
Been following along with Dan Shiffmans videos, trying to brush up on my Object orientated programing using classes.
Ive wrote some code that generates bubbles with random diameters at random positions, using p5's noise function to give the bubbles some movement.
My intention is for the bubbles to pop (be removed from the array with splice()) every time a bubble reaches the edges of the canvas or when two or more bubbles intersect.
The code run as desired, but after a while it crashes throwing up the error "Uncaught TypeError: Cannot read property 'x' of undefined (sketch: line 15)"
Ive tried hacking around but no joy, if anyone could shed some light on why this error occurs, or general pointers on my approach i would be most grateful. Here's the code in question.
var balls = [];
function setup() {
createCanvas(400, 400);
}
function draw() {
background(220);
for (var i = 0; i < balls.length; i++) {
balls[i].showBall();
balls[i].moveBall();
for (var j = 0; j < balls.length; j++) {
if (balls[i].x < 0 + balls[i].r ||
balls[i].x > width - balls[i].r ||
balls[i].y < 0 + balls[i].r ||
balls[i].y > height - balls[i].r ||
balls[i].life >= 220 ||
i != j && balls[i].intersect(balls[j])) {
balls.splice(i, 1);
}
}
}
}
class Ball {
constructor(x, y, r) {
this.x = x;
this.y = y;
this.r = r;
this.t = 0.0;
this.t2 = 107.0;
this.life = 0;
}
showBall() {
noFill();
stroke(this.life);
strokeWeight(2);
ellipse(this.x, this.y, this.r * 2);
}
moveBall() {
this.x += map(noise(this.t), 0, 1, -1, 1) * 0.5;
this.y += map(noise(this.t2), 0, 1, -1, 1) * 0.5;
this.t += 0.02;
this.life += 0.15;
}
intersect(other) {
if (dist(this.x, this.y, other.x, other.y) < this.r + other.r) {
return true;
} else {
return false;
}
}
}
function randBubbleGen() {
let foo = floor(random(11));
console.log(foo);
if (foo >= 5) {
let b = new Ball(random(41, 359),
random(41, 359),
random(5, 40));
balls.push(b);
}
}
setInterval(randBubbleGen, 1000);
<script src="https://cdn.jsdelivr.net/npm/p5#1.3.1/lib/p5.js"></script>
Thanks!
P
The first part of the problem is a classic splice issue:
const arr = [..."abcde"];
for (let i = 0; i < arr.length; i++) {
if (i === 2) { // some arbitrary condition
arr.splice(i, 1);
}
else {
console.log(i, arr[i]);
}
}
What happened here? After splicing the element at index 2, "c", the length of arr becomes 4, yet i++ still happens, skipping an element "d" which is never printed or visited in the loop. The solution is to i-- after each splicing operation or iterate in reverse so that splice doesn't cause unvisited elements to be skipped.
As for your error, the problem is that your inner loop over all j splices out an element, then continues on, acting as if balls[i] wasn't just removed. Based on the above demonstration, we know that after balls.splice(i, 1), balls[i] becomes the next element after the original i for the rest of that iteration of the outer loop body. This is a bug because some collisions will be skipped for i+1 after the spliced element, but won't cause errors unless i happens to be the last element in the balls array. In that case, balls[i+1] is undefined and you can't access properties on undefined.
The solution is to break out of the inner j loop after splicing out an element. That's in addition to iterating in reverse or using i-- after each splice call to avoid skipping balls.
From a time complexity standpoint, splice is a poor choice because it's O(n). If you have n collisions in the balls array, you'll need to loop over it a factor of n times, causing a triply-nested loop running in your update code.
A better general approach is to create a new array of elements that survived a round, then resassign that new array to the old balls array after the frame. This involves some allocation overhead.
Other tips:
Use const instead of let wherever possible.
Use let instead of var for loop counters and mutable variables. Ideally, never use var or let, although p5 promotes mutability due to its window-attached functions.
Prefer forEach and for ... of loops to classical C-style for loops.
You can return dist(this.x, this.y, other.x, other.y) < this.r + other.r since it's already a boolean, no need for if bool return true else return false verbosity.
Keep rendering and position updating separate as much as possible. It probably doesn't matter much for this animation, but as things get more complex, it can be odd when something dies but still gets rendered for a frame as is the case here.
Move the collision detection and edge detection to external functions -- the if (balls[i].x < 0 + balls[i].r ... condition is difficult to read.
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 three.js.
I have two mesh geometries in my scene.
If these geometries are intersected (or would intersect if translated) I want to detect this as a collision.
How do I go about performing collision detection with three.js? If three.js does not have collision detection facilities, are there other libraries I might use in conjuction with three.js?
In Three.js, the utilities CollisionUtils.js and Collisions.js no longer seem to be supported, and mrdoob (creator of three.js) himself recommends updating to the most recent version of three.js and use the Ray class for this purpose instead. What follows is one way to go about it.
The idea is this: let's say that we want to check if a given mesh, called "Player", intersects any meshes contained in an array called "collidableMeshList". What we can do is create a set of rays which start at the coordinates of the Player mesh (Player.position), and extend towards each vertex in the geometry of the Player mesh. Each Ray has a method called "intersectObjects" which returns an array of objects that the Ray intersected with, and the distance to each of these objects (as measured from the origin of the Ray). If the distance to an intersection is less than the distance between the Player's position and the geometry's vertex, then the collision occurred on the interior of the player's mesh -- what we would probably call an "actual" collision.
I have posted a working example at:
http://stemkoski.github.io/Three.js/Collision-Detection.html
You can move the red wireframe cube with the arrow keys and rotate it with W/A/S/D. When it intersects one of the blue cubes, the word "Hit" will appear at the top of the screen once for every intersection as described above. The important part of the code is below.
for (var vertexIndex = 0; vertexIndex < Player.geometry.vertices.length; vertexIndex++)
{
var localVertex = Player.geometry.vertices[vertexIndex].clone();
var globalVertex = Player.matrix.multiplyVector3(localVertex);
var directionVector = globalVertex.subSelf( Player.position );
var ray = new THREE.Ray( Player.position, directionVector.clone().normalize() );
var collisionResults = ray.intersectObjects( collidableMeshList );
if ( collisionResults.length > 0 && collisionResults[0].distance < directionVector.length() )
{
// a collision occurred... do something...
}
}
There are two potential problems with this particular approach.
(1) When the origin of the ray is within a mesh M, no collision results between the ray and M will be returned.
(2) It is possible for an object that is small (in relation to the Player mesh) to "slip" between the various rays and thus no collision will be registered. Two possible approaches to reduce the chances of this problem are to write code so that the small objects create the rays and do the collision detection effort from their perspective, or include more vertices on the mesh (e.g. using CubeGeometry(100, 100, 100, 20, 20, 20) rather than CubeGeometry(100, 100, 100, 1, 1, 1).) The latter approach will probably cause a performance hit, so I recommend using it sparingly.
I hope that others will contribute to this question with their solutions to this question. I struggled with it for quite a while myself before developing the solution described here.
An updated version of Lee's answer that works with latest version of three.js
for (var vertexIndex = 0; vertexIndex < Player.geometry.attributes.position.array.length; vertexIndex++)
{
var localVertex = new THREE.Vector3().fromBufferAttribute(Player.geometry.attributes.position, vertexIndex).clone();
var globalVertex = localVertex.applyMatrix4(Player.matrix);
var directionVector = globalVertex.sub( Player.position );
var ray = new THREE.Raycaster( Player.position, directionVector.clone().normalize() );
var collisionResults = ray.intersectObjects( collidableMeshList );
if ( collisionResults.length > 0 && collisionResults[0].distance < directionVector.length() )
{
// a collision occurred... do something...
}
}
This really is far too broad of a topic to cover in a SO question, but for the sake of greasing the SEO of the site a bit, here's a couple of simple starting points:
If you want really simple collision detection and not a full-on physics engine then check out (link removed due to no more existing website)
If, on the other hand you DO want some collision response, not just "did A and B bump?", take a look at (link removed due to no more existing website), which is a super easy to use Ammo.js wrapper built around Three.js
only works on BoxGeometry and BoxBufferGeometry
create the following function:
function checkTouching(a, d) {
let b1 = a.position.y - a.geometry.parameters.height / 2;
let t1 = a.position.y + a.geometry.parameters.height / 2;
let r1 = a.position.x + a.geometry.parameters.width / 2;
let l1 = a.position.x - a.geometry.parameters.width / 2;
let f1 = a.position.z - a.geometry.parameters.depth / 2;
let B1 = a.position.z + a.geometry.parameters.depth / 2;
let b2 = d.position.y - d.geometry.parameters.height / 2;
let t2 = d.position.y + d.geometry.parameters.height / 2;
let r2 = d.position.x + d.geometry.parameters.width / 2;
let l2 = d.position.x - d.geometry.parameters.width / 2;
let f2 = d.position.z - d.geometry.parameters.depth / 2;
let B2 = d.position.z + d.geometry.parameters.depth / 2;
if (t1 < b2 || r1 < l2 || b1 > t2 || l1 > r2 || f1 > B2 || B1 < f2) {
return false;
}
return true;
}
use it in conditional statements like this:
if (checkTouching(cube1,cube2)) {
alert("collision!")
}
I have an example using this at https://3d-collion-test.glitch.me/
Note: if you rotate(or scale) one (or both) of the cubes/prisims, it will detect as though they haven't been turned(or scaled)
since my other answer is limited I made something else that is more accurate and only returns true when there is a collision and false when there isn't (but sometimes when There still is)
anyway, First make The Following Function:
function rt(a,b) {
let d = [b];
let e = a.position.clone();
let f = a.geometry.vertices.length;
let g = a.position;
let h = a.matrix;
let i = a.geometry.vertices;
for (var vertexIndex = f-1; vertexIndex >= 0; vertexIndex--) {
let localVertex = i[vertexIndex].clone();
let globalVertex = localVertex.applyMatrix4(h);
let directionVector = globalVertex.sub(g);
let ray = new THREE.Raycaster(e,directionVector.clone().normalize());
let collisionResults = ray.intersectObjects(d);
if ( collisionResults.length > 0 && collisionResults[0].distance < directionVector.length() ) {
return true;
}
}
return false;
}
that above Function is the same as an answer in this question by
Lee Stemkoski (who I am giving credit for by typing that) but I made changes so it runs faster and you don't need to create an array of meshes. Ok step 2: create this function:
function ft(a,b) {
return rt(a,b)||rt(b,a)||(a.position.z==b.position.z&&a.position.x==b.position.x&&a.position.y==b.position.y)
}
it returns true if the center of mesh A isn't in mesh B AND the center of mesh B isn't in A OR There positions are equal AND they are actually touching. This DOES still work if you scale one (or both) of the meshes.
I have an example at: https://3d-collsion-test-r.glitch.me/
It seems like this has already been solved but I have an easier solution if you are not to comfortable using ray casting and creating your own physics environment.
CANNON.js and AMMO.js are both physics libraries built on top of THREE.js. They create a secondary physics environment and you tie your object positions to that scene to emulate a physics environment. the documentation is simple enough to follow for CANNON and it is what I use but it hasnt been updated since it was released 4 years ago. The repo has since been forked and a community keeps it updated as cannon-es. I will leave a code snippet here so you can see how it works
/**
* Floor
*/
const floorShape = new CANNON.Plane()
const floorBody = new CANNON.Body()
floorBody.mass = 0
floorBody.addShape(floorShape)
floorBody.quaternion.setFromAxisAngle(
new CANNON.Vec3(-1,0,0),
Math.PI / 2
)
world.addBody(floorBody)
const floor = new THREE.Mesh(
new THREE.PlaneGeometry(10, 10),
new THREE.MeshStandardMaterial({
color: '#777777',
metalness: 0.3,
roughness: 0.4,
envMap: environmentMapTexture
})
)
floor.receiveShadow = true
floor.rotation.x = - Math.PI * 0.5
scene.add(floor)
// THREE mesh
const mesh = new THREE.Mesh(
sphereGeometry,
sphereMaterial
)
mesh.scale.set(1,1,1)
mesh.castShadow = true
mesh.position.copy({x: 0, y: 3, z: 0})
scene.add(mesh)
// Cannon
const shape = new CANNON.Sphere(1)
const body = new CANNON.Body({
mass: 1,
shape,
material: concretePlasticMaterial
})
body.position.copy({x: 0, y: 3, z: 0})
world.addBody(body)
This makes a floor and a ball but also creates the same thing in the CANNON.js enironment.
const tick = () =>
{
const elapsedTime = clock.getElapsedTime()
const deltaTime = elapsedTime - oldElapsedTime
oldElapsedTime = elapsedTime
// Update Physics World
mesh.position.copy(body.position)
world.step(1/60,deltaTime,3)
// Render
renderer.render(scene, camera)
// Call tick again on the next frame
window.requestAnimationFrame(tick)
}
After this you just update the position of your THREE.js scene in the animate function based on the position of your physics scene.
Please check out the documentation as it might seem more complicated than it really is. Using a physics library is going to be the easiest way to simulate collisions. Also check out Physi.js, I have never used it but it is supposed to be a more friendly library that doesn't require you to make a secondary environment
In my threejs version, I only have geometry.attributes.position.array and not geometry.vertices. To convert it to vertices, I use the following TS function:
export const getVerticesForObject = (obj: THREE.Mesh): THREE.Vector3[] => {
const bufferVertices = obj.geometry.attributes.position.array;
const vertices: THREE.Vector3[] = [];
for (let i = 0; i < bufferVertices.length; i += 3) {
vertices.push(
new THREE.Vector3(
bufferVertices[i] + obj.position.x,
bufferVertices[i + 1] + obj.position.y,
bufferVertices[i + 2] + obj.position.z
)
);
}
return vertices;
};
I pass in the object's position for each dimension because the bufferVertices by default are relative to the object's center, and for my purposes I wanted them to be global.
I also wrote up a little function to detect collisions based on vertices. It optionally samples vertices for very involved objects, or checks for proximity of all vertices to the vertices of the other object:
const COLLISION_DISTANCE = 0.025;
const SAMPLE_SIZE = 50;
export const detectCollision = ({
collider,
collidables,
method,
}: DetectCollisionParams): GameObject | undefined => {
const { geometry, position } = collider.obj;
if (!geometry.boundingSphere) return;
const colliderCenter = new THREE.Vector3(position.x, position.y, position.z);
const colliderSampleVertices =
method === "sample"
? _.sampleSize(getVerticesForObject(collider.obj), SAMPLE_SIZE)
: getVerticesForObject(collider.obj);
for (const collidable of collidables) {
// First, detect if it's within the bounding box
const { geometry: colGeometry, position: colPosition } = collidable.obj;
if (!colGeometry.boundingSphere) continue;
const colCenter = new THREE.Vector3(
colPosition.x,
colPosition.y,
colPosition.z
);
const bothRadiuses =
geometry.boundingSphere.radius + colGeometry.boundingSphere.radius;
const distance = colliderCenter.distanceTo(colCenter);
if (distance > bothRadiuses) continue;
// Then, detect if there are overlapping vectors
const colSampleVertices =
method === "sample"
? _.sampleSize(getVerticesForObject(collidable.obj), SAMPLE_SIZE)
: getVerticesForObject(collidable.obj);
for (const v1 of colliderSampleVertices) {
for (const v2 of colSampleVertices) {
if (v1.distanceTo(v2) < COLLISION_DISTANCE) {
return collidable;
}
}
}
}
};
You could try cannon.js.It makes it easy to do collision and its my favorite collision detection library. There is also ammo.js too.
I have the script that works with pixels in html5 canvas element. And there is some strange behavior by Google Chrome (version: 17.0.942.0 dev). I have 2 kind of operations with pixels:
changing the hue of pixels inside particular polygone
pixel selection by wand tool
The problem is when I'm changing the hue, the memory of that tab grows after every change up to 250MB (sometimes even more) and than being reseted to the initial size. But when I'm using wand tool selection, the memory doesn't grow, it goes up or down depending how many pixels are selected (this is normal behavior in my opinion). Please help me to understand why memory grows after every change in first case.
P.S. in FF there is no issue like that, that's why I think that this is Chrome-specific "strange" behavior
Here is the code of hue change:
function isPointInPoly(poly, pt){
for(var c = false, i = -1, l = poly.length, j = l - 1; ++i < l; j = i)
((poly[i].y <= pt.y && pt.y < poly[j].y) || (poly[j].y <= pt.y && pt.y < poly[i].y))
&& (pt.x < (poly[j].x - poly[i].x) * (pt.y - poly[i].y) / (poly[j].y - poly[i].y) + poly[i].x)
&& (c = !c);
return c;
}
function changeHue(hue){
var modifyCanvas = $("#canvas-modify").get(0);
var modifyContext = modifyCanvas.getContext('2d');
modifyContext.clearRect(0, 0, modifyCanvas.width, modifyCanvas.height);
var imageData = $$.mainCanvasContext.getImageData(0, 0, $$.mainCanvasElem.width, $$.mainCanvasElem.height);
for(var i=0;i<imageData.data.length;i+=4) {
var p = {x: (i/4)%imageData.width, y: parseInt((i/4)/imageData.width)};
for(var j=0;j<$$.globalSelection.length;j++){
var poly = $$.globalSelection[j].slice(0, $$.globalSelection[j].length-1);
if(isPointInPoly(poly, p)) {
var hsl = rgbToHsl(imageData.data[i], imageData.data[i+1], imageData.data[i+2]);
hsl[0] = hue;
var rgb = hslToRgb(hsl[0], hsl[1], hsl[2]);
imageData.data[i] = rgb[0];
imageData.data[i+1] = rgb[1];
imageData.data[i+2] = rgb[2];
} else {
imageData.data[i] = 0;
imageData.data[i+1] = 0;
imageData.data[i+2] = 0;
imageData.data[i+3] = 0;
}
}
}
modifyContext.putImageData(imageData, 0, 0);
}
It's not just you.
There have been off and on memory leaks in Chrome with anything canvas imageData related.
For instance:
http://code.google.com/p/chromium/issues/detail?id=51171
http://code.google.com/p/chromium/issues/detail?id=20067
etc.
Chromium's issue tracker policies are weird. The issues aren't necessarily fixed even though they close them.
It's possible its a webkit thing and not a Chrome thing but I can't say for certain. All I can say is that you yourself aren't doing anything wrong.
Though while we're here let me say that you should not be doing this:
var modifyCanvas = $("#canvas-modify").get(0);
var modifyContext = modifyCanvas.getContext('2d');
Every time, for performance's sake. Especially if this operation happens often.