Real Time Fluid Dynamics in Swift for iOS
Join the DZone community and get the full member experience.
Join For Free
Looking at the amazing success Joseph Lord has had speeding up CPU based cellular automata, I thought it would be an interesting project to port some old ActionScript 3 fluid dynamics code to Swift. The original AS3 port was by Oaxoa and based on Jos Stam's Real-Time Fluid Dynamics for Games. I've used it many times in AS3 projects over the years but have never really tweaked it.
My iteration is almost a direct copy of the original AS3 code, but I've spent some time optimising it a little further.
The CFD solver uses a handful of arrays for horizontal and vertical direction (u and v) and density (d). In some cases, the original code would invoke the same function, for example diffuse() and advect(), consecutively targeting the u and v arrays separately. So, at the expense of code reuse, I created near duplicate copies of those functions that target u and v simultaneously.
The original code made extensive use of a small function to get the index of a one dimensional array based on x and y parameters. I've essentially inlined a lot of uses of this and kept an eye on duplicate calculations - creating constants for re-used values.
As with my other recent Swift projects, both the solving and the rendering are done in separate threads. I'm using Tobias Due Munk'sAsync library which abstract Grand Central Dispatch to handle threads and code based on Joseph's bitmap data generation code for rendering.
The user interface is pretty basic - there's a single UIImageView to hold the rendered fluid densities and a reset button to fire off the start 'explosion'.
Inside the ViewController, I've overridden the touchesMoved() method. This allows me to pick up any touch events on the UIImageView like so:
override func touchesMoved(touches: NSSet!, withEvent event: UIEvent!) { let touch = event.allTouches().anyObject().locationInView(uiImageView); let touchX = Int(touch.x / 3); let touchY = Int(touch.y / 3); [...] }
I'm not entirely sure why I need to multiply the values by 3 - I suspect it's related to the rumoured resolution of the new Apple iOS devices - but touchX and touchY are set to the coordinates in the CFD arrays so I can easily bump up the density where the user has touched.
I also keep a note of the previous touch coordinates so that I can use the delta to update the u and v arrays to make touch events change the direction of the fluid flow. Because the previous touch positions are optional, I've made use of optional binding inside the overridden touchesMoved():
[...] if let ptx = previousTouchX { if let pty = previousTouchY { u[targetIndex] = u[targetIndex] + Double((touchX - ptx) / 2) v[targetIndex] = v[targetIndex] + Double((touchY - pty) / 2) } } [...]
...and once the touch gesture has finished, I can null the previous values:
override func touchesEnded(touches: NSSet!, withEvent event: UIEvent!) { previousTouchX = nil; previousTouchY = nil; }
There's a bit of a mixture between using and not using semi-colons. My job is ActionScript coding during the day and adding them is second nature, so please forgive the inconsistency.
The code is written in XCode 6 Beta 6 and in the iOS simulator on my iMac, each step takes around 0.04 of a second for a 200 x 200 grid. There is more optimisation to come, but if you have a chance to run it on a recent A7 based iPad, I'd love to hear the results.
All the source code is available here at my GitHub repository.
Published at DZone with permission of Simon Gladman, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments