Jump to content

WebWorkers and Particles


Sharpleaf
 Share

Recommended Posts

I was reading up on WebWorkers and I feel like they could be used to process particles and alleviate the strain that function currently has on my animation loop.

 

I have a few hundred particles floating around in various ParticleContainers (they're magical little "motes" that add to the ambiance). Every animation loop iteration, I have to update their position among many other things that have to happen in the same loop. When looking at what takes the most time per frame, updating the positions of the particles by far takes up the most time. 

 

I was wondering if I could offload the processing of the particles positions to a WebWorker. However, I'm not sure how to pass the data back and forth appropriately becuase WEbWorkers don't see any globals or other variables in other scripts. And, if I passed updated messages from the worker to the main thread and then the main thread had to "Apply" those changes, wouldn't that be just as slow?

 

Does anyone have any input on how or if this could work?

 

Thanks!

Link to comment
Share on other sites

How exactly are you updating them?

 

 

Basically, I loop through every child in each of the 3 ParticleContainers, and then I update the position, rotation, and alpha of each child. There's also code in there that check where they are so that if they go offscreen, they "wrap around" to the other side.

 

More specifically.... (here's the function, it gets called every RAF loop.

function animateMotes(){		if(intent)	if(intent == "arena" || intent.indexOf("adventure") >= 0)		return false;			if(mote_container1)	for(var c in mote_container1.children){		var mote = mote_container1.children[c];			 	if(mote.position.x <= -200 || mote.position.x >= mote_container1.width + 200 || mote.position.y <= -200 || mote.position.y >= renderer.height + 200){	 			 		if(mote.position.x >= mote_container1.width + 200){		 		mote.visible = false;		 		mote.position.x = 0 - Math.round(Math.random()*100);	 				 		}else{	 			if(mote.position.x <= -200){			 		mote.visible = false;			 		mote.position.x = mote_container1.width + Math.round(Math.random()*100);	 	 			}	 		}				 		if(mote.position.y >= mote_container1.height + 200){		 		mote.visible = false;		 		mote.position.y = 0 - Math.round(Math.random()*100);	 				 		}else{	 			if(mote.position.y <= -200){			 		mote.visible = false;			 		mote.position.y = mote_container1.height + Math.round(Math.random()*100);	 	 			}	 		}					 	}else{	 		mote.visible = true;			var cur_t = (new Date()).getTime()/1000;			var distance_traveled = mote.speed * (cur_t - mote.last_run);						mote.position.x -= distance_traveled * 20;			mote.position.y += Math.sin(mote.position.x) * ((Math.random()*0.08)) + mote.angle_quotient;			if(mote.rotation_direction == 1){				mote.rotation += mote.acceleration * (3.14/180);				}else{				mote.rotation -= mote.acceleration * (3.14/180);				}						if(mote.alpha_direction == 1){				if((mote.alpha + mote.alpha_speed) > 1){					mote.alpha = 1;				}else{					mote.alpha += mote.alpha_speed;				}							if(mote.alpha >= mote.alpha_max){					mote.alpha_direction = 2;				}			}else{				mote.alpha -= mote.acceleration/200;				if(mote.alpha <= 0){					mote.alpha_direction = 1;				}			}			mote.last_run = (new Date()).getTime()/1000;	 	}		mote = null;	}		if(mote_container2)	for(var c in mote_container2.children){		var mote = mote_container2.children[c];				if(mote){		 	if(mote.position.x <= -200 || mote.position.x >= mote_container2.width + 200 || mote.position.y <= -200 || mote.position.y >= renderer.height + 200){		 				 		if(mote.position.x >= mote_container2.width + 200){			 		mote.visible = false;			 		mote.position.x = 0 - Math.round(Math.random()*100);	 					 		}else{		 			if(mote.position.x <= -200){				 		mote.visible = false;				 		mote.position.x = mote_container2.width + Math.round(Math.random()*100);	 		 			}		 		}						 		if(mote.position.y >= mote_container2.height + 200){			 		mote.visible = false;			 		mote.position.y = 0 - Math.round(Math.random()*100);	 					 		}else{		 			if(mote.position.y <= -200){				 		mote.visible = false;				 		mote.position.y = mote_container2.height + Math.round(Math.random()*100);	 		 			}		 		}				 	}else{		 		mote.visible = true;				var cur_t = (new Date()).getTime()/1000;				var distance_traveled = mote.speed * (cur_t - mote.last_run);								mote.position.x -= distance_traveled * 5;				mote.position.y += Math.sin(mote.position.x) * ((Math.random()*.05)) + mote.angle_quotient;				if(mote.rotation_direction == 1){					mote.rotation += mote.acceleration * (3.14/180);					}else{					mote.rotation -= mote.acceleration * (3.14/180);					}				if(mote.alpha_direction == 1){					if((mote.alpha + mote.alpha_speed) > 1){						mote.alpha = 1;					}else{						mote.alpha += mote.alpha_speed;					}					if(mote.alpha >= mote.alpha_max){						mote.alpha_direction = 2;					}				}else{					mote.alpha -= mote.alpha_speed;					if(mote.alpha <= 0){						mote.alpha_direction = 1;					}				}				mote.last_run = (new Date()).getTime()/1000;		 	}		}		mote = null;	}		if(mote_container3)	for(var c in mote_container3.children){		var mote = mote_container3.children[c];	 	if(mote)	 	if(mote.position.x <= -200 || mote.position.x >= mote_container3.width + 200 || mote.position.y <= -200 || mote.position.y >= renderer.height + 200){	 			 		if(mote.position.x >= mote_container3.width + 200){		 		mote.visible = false;		 		mote.position.x = 0 - Math.round(Math.random()*100);	 				 		}else{	 			if(mote.position.x <= -200){			 		mote.visible = false;			 		mote.position.x = mote_container3.width + Math.round(Math.random()*100);	 	 			}	 		}				 		if(mote.position.y >= mote_container3.height + 200){		 		mote.visible = false;		 		mote.position.y = 0 - Math.round(Math.random()*100);	 				 		}else{	 			if(mote.position.y <= -200){			 		mote.visible = false;			 		mote.position.y = mote_container3.height + Math.round(Math.random()*100);	 	 			}	 		}	 			 	}else{	 		mote.visible = true;			var cur_t = (new Date()).getTime()/1000;			var distance_traveled = mote.speed * (cur_t - mote.last_run);						mote.position.x -= distance_traveled * 2.4;			mote.position.y += Math.sin(mote.position.x) * ((Math.random()*0.02))+ mote.angle_quotient;			if(mote.rotation_direction == 1){				mote.rotation += mote.acceleration * (3.14/180);				}else{				mote.rotation -= mote.acceleration * (3.14/180);				}			if(mote.alpha_direction == 1){				if((mote.alpha + mote.alpha_speed) > 1){					mote.alpha = 1;				}else{					mote.alpha += mote.alpha_speed;				}				if(mote.alpha >= mote.alpha_max){					mote.alpha_direction = 2;				}			}else{				mote.alpha -= mote.acceleration/150;				if(mote.alpha <= 0){					mote.alpha_direction = 1;				}			}			mote.last_run = (new Date()).getTime()/1000;	 	}		mote = null;	}	}
Link to comment
Share on other sites

How many motes there are?

 

Replace "(new Date()).getTime()" to "Date.now()" that will give you 5%.. I think 30% is spent somewhere in Math.round(Math.random()).

 

Im thinking of how to move that to shader side. Update mote only 5-10 times and store its velocity and other params in vertex buffer

Link to comment
Share on other sites

Yep, that's it - store mote velocity and dont change it too often. Add uniform that will store current time. Modify shader, make

 

glPosition = (projectionMatrix * vec3(( vertexPosition + uniform_time_from_last_update * attribute_velocity ).xy, 1)).xy

 

instead of just

 

glPosition = (projectionMatrix * vec3(vertexPosition, 1.0)).xy

Link to comment
Share on other sites

Yep, that's it - store mote velocity and dont change it too often. Add uniform that will store current time. Modify shader, make

 

glPosition = (projectionMatrix * vec3(( vertexPosition + uniform_time_from_last_update * attribute_velocity ).xy, 1)).xy

 

instead of just

 

glPosition = (projectionMatrix * vec3(vertexPosition, 1.0)).xy

 

Wow! I uhh... don't understand.... :)

So, I'm not sure what a shader, vertex,or uniform really are. So... can I ask for a more laymans explanation of what you mean?

Link to comment
Share on other sites

Shader runs on GPU, it can calculate current position of your mote based on what did you compute on CPU.

 

"I'm not sure how to pass the data back and forth appropriately" - you can pass objects between window and worker with zero-copy (object/buffer/array/whatever) disappears from one js heap and shows in other. But I recommend to target on webgl devices and move some part of work to the shader.

 

And you still havent told me how big is their numberr.

 

I can code you a demo for a beer, just give me your mote texture :)

Link to comment
Share on other sites

And you still havent told me how big is their numberr.

 

 

Sorry :) There is a total of 80 between the 3 containers. Though, there' s ONLY 80 because if there are too many more, performance suffers.... we would like more in the background

I will ABSOLUTELY buy you a beer if you help us (again!!) Wold you like to chat in an outside room? Maybe Google Hangouts?

Link to comment
Share on other sites

80 motes is very small amount.

 

Every time you call mote_container3.width and mote_container3.height , it calculates getLocalBounds() which calls every child updateTransform() because it needs LOCAL bounds.

 

So your complexity is MOTES^2 of updateTransform() which sucks.

 

Using getBounds() and caching width/height will help performance, BUT that values are afffected by motes too.

 

Lets discuss it in hangouts.

Link to comment
Share on other sites

For posterity, here's what solved my performance problems!

We overwrote the getLocalBounds function of my mote containers.

 

 

var rect = new PIXI.Rectangle(0, 0, renderer.width, renderer.height);frontMoteStage.getLocalBounds = function() { return rect; }; mote_container1.getLocalBounds = function() { return rect; };  mote_container2.getLocalBounds = function() { return rect;};mote_container3.getLocalBounds = function() { return rect;};frontMoteStage.filterArea = rect;

frontMoteStage is the parent of mote_container1 and mote_container2, and and since we update the getLocalBounds, it's _bounds is not reporting the correct values. Since Filters need that value, that's why we added the frontMoteStage.filterArea = rect;

Link to comment
Share on other sites

Why couldn't you just use renderer.width instead of container.width instead of hacking them into being the same?

 

 

In my function which updates the particles positions, I do now use renderer.width and height instead of the container.

I think the hack is to make sure that if anything ever calls the containers .width or .height, that updateTransform doesn't get called on every child in the ParticleContainer as it does by default.

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...