Sharpleaf Posted November 5, 2015 Share Posted November 5, 2015 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! Quote Link to comment Share on other sites More sharing options...
ivan.popelyshev Posted November 5, 2015 Share Posted November 5, 2015 How exactly are you updating them? What info is required for it? Quote Link to comment Share on other sites More sharing options...
Sharpleaf Posted November 5, 2015 Author Share Posted November 5, 2015 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; } } Quote Link to comment Share on other sites More sharing options...
Sharpleaf Posted November 5, 2015 Author Share Posted November 5, 2015 if you go to the Monster Island game and move the island around, the little floating things in the foreground and behind the island are the particles I'm messing with. Quote Link to comment Share on other sites More sharing options...
ivan.popelyshev Posted November 5, 2015 Share Posted November 5, 2015 May be its GC problem. (new Date()).getTime() -> Date.now() Quote Link to comment Share on other sites More sharing options...
Sharpleaf Posted November 5, 2015 Author Share Posted November 5, 2015 May be its GC problem. (new Date()).getTime() -> Date.now() Hey Ivan, I don't understand what you mean :/ .... could you explain? Quote Link to comment Share on other sites More sharing options...
ivan.popelyshev Posted November 5, 2015 Share Posted November 5, 2015 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 Quote Link to comment Share on other sites More sharing options...
ivan.popelyshev Posted November 5, 2015 Share Posted November 5, 2015 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 Quote Link to comment Share on other sites More sharing options...
Sharpleaf Posted November 5, 2015 Author Share Posted November 5, 2015 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? Quote Link to comment Share on other sites More sharing options...
ivan.popelyshev Posted November 5, 2015 Share Posted November 5, 2015 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 Quote Link to comment Share on other sites More sharing options...
Sharpleaf Posted November 5, 2015 Author Share Posted November 5, 2015 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 backgroundI will ABSOLUTELY buy you a beer if you help us (again!!) Wold you like to chat in an outside room? Maybe Google Hangouts? Quote Link to comment Share on other sites More sharing options...
ivan.popelyshev Posted November 5, 2015 Share Posted November 5, 2015 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. Quote Link to comment Share on other sites More sharing options...
Sharpleaf Posted November 5, 2015 Author Share Posted November 5, 2015 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; Quote Link to comment Share on other sites More sharing options...
xerver Posted November 5, 2015 Share Posted November 5, 2015 Why couldn't you just use renderer.width instead of container.width instead of hacking them into being the same? Quote Link to comment Share on other sites More sharing options...
Sharpleaf Posted November 5, 2015 Author Share Posted November 5, 2015 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. Quote Link to comment Share on other sites More sharing options...
ivan.popelyshev Posted November 6, 2015 Share Posted November 6, 2015 Why couldn't you just use renderer.width instead of container.width instead of hacking them into being the same? He did that. Problem is, when he stopped calling .width, childrens updates stopped working => filter stopped working too. That was very fun to debug Quote Link to comment Share on other sites More sharing options...
d13 Posted November 7, 2015 Share Posted November 7, 2015 Hmmm.... motes?Have you looked into Brownian motion? http://stackoverflow.com/questions/9605109/particle-brownian-motion-with-directions and https://books.google.ca/books?id=KZTIFYMLShYC&pg=PA446&lpg=PA446&dq=Brownian+motion+javascript&source=bl&ots=-dMGW9iAep&sig=rgwGvUlHr07tuC6zaaw7IvXaxrA&hl=en&sa=X&ved=0CCEQ6AEwATgKahUKEwjPyb_Ti_3IAhWFWx4KHRgjCsE#v=onepage&q=Brownian%20motion%20javascript&f=false It's probably not exactly what you want but it's a very CPU efficient way of simulating dust particles. Quote Link to comment Share on other sites More sharing options...
Recommended Posts
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.