Liquid Simulation (the real thing!) Working in Corona SDK.

So this is what you get with a little resources and tons of imagination!
Thanks to every one on this forum.

Enjoy it!

If you like it an use it in you code. Please a mention of my help it will be must appreciated (not mandatory :slight_smile: )

Here it is the code (paste it in a new main.lua):

[lua]-------------------------------------------------------------------

– Copyright 2011 Emilio Aguirre, All Rights Reserved.
– www.emilioaguirre.com

– Permission is hereby granted, free of charge, to any person obtaining a copy of
– this software and associated documentation files (the “Software”), to deal in the
– Software without restriction, including without limitation the rights to use, copy,
– modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
– and to permit persons to whom the Software is furnished to do so, subject to the
– following conditions:

– The above copyright notice and this permission notice shall be included in all copies
– or substantial portions of the Software.

– THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
– IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
– FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
– AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
– LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
– OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
– THE SOFTWARE.

– This is a demo of a liquid simulation.
– Touch the screen to see the gravity changes, the liquid will go
– in the opposite direction.


local physics = require(“physics”)

– Define variables
local imgBuffer = {} – Image buffer
local w1= math.floor(display.contentWidth)
local h1= math.floor(display.contentHeight)
local refreshImage – Flag to handle when to draw
local line={} – Lines table

– Start Physics
physics.start()

– Hide status bar
display.setStatusBar( display.HiddenStatusBar )

– Create background rect
local bg = display.newRect( 0, 0, 320, 480 )
bg:setFillColor( 0, 127, 255)

– Create the border to prevent the liquid to fall
borderCollisionFilter = { categoryBits = 1, maskBits = 6 } – collides with (4 & 2) only
borderBodyElement = { friction=0.1, bounce=0.2, filter=borderCollisionFilter }

local borderTop = display.newRect( 0, 0, 320, 1 )
borderTop:setFillColor( 0, 0, 0, 0)
physics.addBody( borderTop, “static”, borderBodyElement )

local borderBottom = display.newRect( 0, 479, 320, 1 )
borderBottom:setFillColor( 0, 0, 0, 0)
physics.addBody( borderBottom, “static”, borderBodyElement )

local borderLeft = display.newRect( 0, 1, 1, 480 )
borderLeft:setFillColor( 0, 0, 0, 0)
physics.addBody( borderLeft, “static”, borderBodyElement )

local borderRight = display.newRect( 319, 1, 1, 480 )
borderRight:setFillColor( 0, 0, 0, 0)
physics.addBody( borderRight, “static”, borderBodyElement )

– Add anoter wall in the middle
local wall = display.newRect( 100, 350, 140, 20 )
wall:setFillColor( 255, 255, 0)
physics.addBody( wall, “static”, borderBodyElement )


– Create 100 balls that will serve as the liquid elements

local balls = {}
local ballsCollisionFilter = { categoryBits = 2, maskBits = 3 } – collides with (2 & 1) only
local ballsBody = { density=2, friction=0.1, bounce=0.1, radius=5, filter=ballsCollisionFilter }
local j = 0
for i = 1,100 do
balls[i] = display.newCircle(160, 200 + math.random(20),5)
balls[i]:setFillColor(0,127,255, 0) – To make the balls transparent add 0 as the fourth element.
balls[i].myName = “ball”
physics.addBody( balls[i], ballsBody )
end

– Draw a set of lines (like flood fill) depending if the pixel in
– the image buffer is greater than a threshold.

function fill(img,w,h)
local threshold = 22 – Threshold (best results from 20 to 30)
local st = 0 – Flag to indicate if a start has being found
local x1,y1 – Start line
local x2,y2 – End line

– Traverse the rows of the image buffer
for y=0,h-1 do
st = 0
– Traverse the columns of the image buffer
for x=0,w-1 do
– if item exist and is greater than threshold
if img[(y*w)+x+1] and img[(y*w)+x+1]>threshold then
– There is no start yet
if st == 0 then
st = 1
– Now we have a start
x1 = x
y1 = y
x2 = x
y2 = y
else
– Grow then end of line with the current pixel
x2 = x
y2 = y
end
else
– Do we have a line?
if st == 1 then
– Yes draw it.
line[#line+1] = display.newLine(x1,y1,x2,y2)
– Clear the flag
st = 0
end
end
end
– Check if a missing line needs to be drawn
if st == 1 then
line[#line+1] = display.newLine( x1,y1,x2,y2)
end
end
end


– Draw a circle mask inside a image buffer

local mask = {
0,0,0,0,0,0,0,1,2,2,2,2,1,0,0,0,0,0,0,0,
0,0,0,0,0,2,4,6,7,8,8,7,6,4,2,0,0,0,0,0,
0,0,0,0,4,6,9,11,12,13,13,12,11,9,6,4,0,0,0,0,
0,0,0,4,8,11,13,16,17,18,18,17,16,13,11,8,4,0,0,0,
0,0,4,8,11,15,18,20,22,23,23,22,20,18,15,11,8,4,0,0,
0,2,6,11,15,19,22,25,27,28,28,27,25,22,19,15,11,6,2,0,
0,4,9,13,18,22,26,29,32,33,33,32,29,26,22,18,13,9,4,0,
1,6,11,16,20,25,29,33,36,38,38,36,33,29,25,20,16,11,6,1,
2,7,12,17,22,27,32,36,40,43,43,40,36,32,27,22,17,12,7,2,
2,8,13,18,23,28,33,38,43,47,47,43,38,33,28,23,18,13,8,2,
2,8,13,18,23,28,33,38,43,47,47,43,38,33,28,23,18,13,8,2,
2,7,12,17,22,27,32,36,40,43,43,40,36,32,27,22,17,12,7,2,
1,6,11,16,20,25,29,33,36,38,38,36,33,29,25,20,16,11,6,1,
0,4,9,13,18,22,26,29,32,33,33,32,29,26,22,18,13,9,4,0,
0,2,6,11,15,19,22,25,27,28,28,27,25,22,19,15,11,6,2,0,
0,0,4,8,11,15,18,20,22,23,23,22,20,18,15,11,8,4,0,0,
0,0,0,4,8,11,13,16,17,18,18,17,16,13,11,8,4,0,0,0,
0,0,0,0,4,6,9,11,12,13,13,12,11,9,6,4,0,0,0,0,
0,0,0,0,0,2,4,6,7,8,8,7,6,4,2,0,0,0,0,0,
0,0,0,0,0,0,0,1,2,2,2,2,1,0,0,0,0,0,0,0
}

– Draw a mask circle over the image buffer

– pix - image buffer
– w - width of the image buffer
– h - height of the image buffer
– ox - x of the physical object
– oy - y of the physical object

function drawCircle(pix,w,h,ox,oy)
local y,x,ny,nx,index,maskIdx
for y=-9,10 do
for x=-9,10 do
ny = y+oy
nx = x+ox
if (nx>0 and nx<=w and ny>0 and ny<=h) then
index = (ny*w)+nx+1
maskIdx = ((y + 9)*20)+ (x+9) + 1
if pix[index] then
pix[index] = (pix[index] + mask[maskIdx])
else
pix[index] = mask[maskIdx]
end
end
end
end
end


– Callback to frame

local onFrameUpdate = function( event )

– Redraw only if a collision has been detected
if refreshImage == 1 then
– Clear the buffer image
imgBuffer={}
– Remove all lines from display
for _,v in pairs(line) do
v.parent:remove( v )
end
line={}
– Draw in the buffer some dots corresponding to the same position
– of the physical objects
for i = 1,#balls do
drawCircle(imgBuffer,w1,h1,math.floor(balls[i].x),math.floor(balls[i].y))
end
– Fill the objects in the display according to the objects in the
– image buffer.
fill(imgBuffer,w1,h1)

– Reset the flag
refreshImage = 0
end
end


– Callback to global collision

local function onGlobalCollision( event )

if (event.object1.myName~=nil and event.object2.myName~=nil) then
– A collision ended let’s refresh the images
if ( event.phase == “ended”) then
– Only refresh if at least we have a collision
refreshImage = 1
end
end

end

– Callback to global accelerometer

local function onTilt( event )
physics.setGravity( 10 * event.xGravity, -10 * event.yGravity )
end


– Callback to frame

local onTouchCallback = function( event )
physics.setGravity( 0.1 * (180 - event.x), 0.1 * (240-event.y))
end

– Create event listeners

Runtime:addEventListener( “enterFrame”, onFrameUpdate )
Runtime:addEventListener( “collision”, onGlobalCollision )
Runtime:addEventListener( “accelerometer”, onTilt )
Runtime:addEventListener( “touch”, onTouchCallback )[/lua] [import]uid: 9975 topic_id: 17023 reply_id: 317023[/import]

Super cool & interesting. You may want to move this to Code Exchange for people looking for awesome and helpful things like this.

Naomi [import]uid: 67217 topic_id: 17023 reply_id: 63894[/import]

i didn’t understand the code but the output is very cool :slight_smile:
if you can add more documentation to the code will be helpful, thanks. [import]uid: 79884 topic_id: 17023 reply_id: 63899[/import]

Click here to see the video of Simulation

A little explanation on how it works:

I create a 100 balls (circle physical objects) and set the collisions with some invisible walls to keep the balls on the screen view.
The balls all together will behave as the liquid.
The trick for melting each ball to simulate the liquid is do as follows:

  • I create an array buffer of size of screen width x screen height coordinates.
  • For each ball I draw a circle mask on it’s ball (x,y) position. The mask is a gray intensity of a blur ball 20x20.
  • The draw is an additive.
  • Then I just traverse the image buffer line by line and draw a display.newLine for the pixels greater than a threshold.
  • This is repeated for every frame.

That’s it.

[import]uid: 9975 topic_id: 17023 reply_id: 63908[/import]

@eaguirre
Thank you for this code share! Very nice effect! [import]uid: 9058 topic_id: 17023 reply_id: 63985[/import]

This looks awesome, but have you tested it on a device? I’m afraid 100 physics objects constantly colliding will give you a serious performance hit because every single frame is rendered. There’s a thread on here somewhere about Frame Rate Independent Movement, which is currently not implemented. But this is yet another amazing example of why we need it. I’d be interested to hear about performance on a device. Great job! [import]uid: 40137 topic_id: 17023 reply_id: 63991[/import]

Yeeepp, this is how it should work! What about preformance increase? Maybe some kind of fast vectors algorithm? [import]uid: 12704 topic_id: 17023 reply_id: 63992[/import]

Very cool! Thanks for sharing! [import]uid: 40033 topic_id: 17023 reply_id: 64006[/import]

A better spawn of water. Simulates running water on a sink.

[lua]-------------------------------------------------------------------
– Create 200 balls that will serve as the liquid elements

local balls = {}
local ballsCollisionFilter = { categoryBits = 2, maskBits = 3 } – collides with (2 & 1) only
local ballsBody = { density=1000, friction=0.12, bounce=0.1, radius=math.random(3,6), filter=ballsCollisionFilter }

local function spawn()
balls[#balls+1] = display.newCircle(160, 200,5)
balls[#balls]:setFillColor(0,127,255, 0) – To make the balls transparent add 0 as the fourth element.
balls[#balls].myName = “ball”
physics.addBody( balls[#balls], ballsBody )
end
local tm = timer.performWithDelay(30, spawn,200)[/lua]

Also if you want a transparent water add this line:
[lua]line[#line]:setColor( 0, 102, 200, 127 )[/lua]
after all instance of the creation of line:
[lua]line[#line+1] = display.newLine(x1,y1,x2,y2)[/lua]
inside the function: [lua] function fill(img,w,h)[/lua]

[import]uid: 9975 topic_id: 17023 reply_id: 64079[/import]

Thanks for this great liquid simulation! However, as for performance, after putting this at the end of my code:
[lua]local num = 1
local function framerate()
print(“This is frame number”…num)
num = num + 1
end
Runtime:addEventListener(“enterFrame”, framerate)
local sec = 1
local function secondcounter()
print(“A second has passed.”)
num = 1
end
t1 = timer.performWithDelay(1000, secondcounter, 0)[/lua]
And reviewing the terminal results, I saw that the framerate usually hovers around 13FPS on my Mac. So, too slow to use for an actual game for now, but still really cool! [import]uid: 38000 topic_id: 17023 reply_id: 64093[/import]

“There’s a thread on here somewhere about Frame Rate Independent Movement, which is currently not implemented. But this is yet another amazing example of why we need it.”

Yes, this exactly. A million times, this. [import]uid: 49447 topic_id: 17023 reply_id: 64099[/import]

Thanks for the frame rate code. But any printing to the console it slows the performance also, so instead of printing “This is a frame number” just change to “f#” or not even any text just the number. That will accelerate the performance without touching the code :slight_smile:

But I still modify the code a made some new changes, take the latest code from the here

http://developer.anscamobile.com/code/liquid-simulation-corona-sdk

and see the new video (where I use 200 balls to the liquid and also show them ):

http://www.youtube.com/watch?v=KMYCPq2w-8s [import]uid: 9975 topic_id: 17023 reply_id: 64558[/import]

The frame rate on the simulator is irrelevant. Has anyone tested this on a device?

@eaguirre Do you have any videos of this on a device? It’s awesome, but with that many physics objects I just don’t see how it can run on a device without a major frame rate lag. I’d love to be proven wrong though! [import]uid: 40137 topic_id: 17023 reply_id: 64608[/import]

Yesterday I test on a device, as it is right now. And get between 3-5 fps.
Then I made some changes (that I will post soon):

  • I realize that having 200 objects at the same time could be problematic. So I decided to use the object with the less footprint instead of using newCircle() so I changed to newGroup() (because it does not have a UI and it’s only a containter but because it is a display object you can still assign a circle body for physics :slight_smile: ) and it seems to help a little bit.

  • I remove all onCollision code and then collision listener because it also takes some cpu times. And because the liquid is almost all the time in contact.

  • And some changes here and there to optimize and remove any multiplication inside a for-loop

With all this changes It speed up on a device between 4-7 fps (android nexus one).

So I figure out that the bottle neck now is the fill() routine.
But there is no much room from improvement here, the only thing I could think is to add a “step” for the for-loops (y -rows and x - colums).

I tested this on the emulator and give me an huge improvement but the results are jaggy and incrementing the step (with values 2,4,6,8) gave me a blocky liquid but fast animation :slight_smile: which still looks cool.

I will try this on my device this afternoon and post the new code also.

So at the end, If you want to create a blocky look game with blocky looking liquid this code will be excellent.

But for the nice stylized liquid that we all want we will need ask to Carlos to add a:

display.newMetaball()

to move this simply code to the native corona.

Cheers,

Emilio [import]uid: 9975 topic_id: 17023 reply_id: 64871[/import]

Sorry, but I don’t think you’ll see much effect changing the fill() routine.

This link explains it:

[import]uid: 40137 topic_id: 17023 reply_id: 64895[/import]

On a 3gs, this runs like…well it barely runs at all :slight_smile:

on a ipod 4gen it gets less than 10fps

ipad 2, gets a little higher, but seems to fluctuate all over the place.

:slight_smile:

Just thought I share that!

Great code example, but it’s obviously not ready for real device usage…yet.

I messed with the fill routine, it seems I could not do anything to boos performance (unless I removed some physics objects).

Basically, I am always after 60 fps or at least high 40’s. I don’t care what people say about not telling the difference. I can tell the difference between 30 and 60 and between 60 and 100+ (after that, I can’t notice). Good thing corona goes to 60 :slight_smile:

If this can get to 30fps, then it translates to less than that once you add in other game elements. Really, if this was 60 fps, then I would expect 30’s and 40’s if I was building a game with other physics bodies and other game elements as well.

That’s usually been what happens to me - prototype shows up at 60fps easy, then starts creeping downwards! Such is life.
This code is really awesome though, don’t get me wrong (needs a speed boost!)!
ng [import]uid: 61600 topic_id: 17023 reply_id: 64925[/import]

>>This code is really awesome though, don’t get me wrong (needs a speed boost!)!

Cuz it’s not possible with current Box2D implementation in Corona SDK. “Own” physics engine with fast vector implementation might help. [import]uid: 12704 topic_id: 17023 reply_id: 64926[/import]

“Own” physics engine with fast vector implementation might help.

Yes, but that’s not exactly “CODE LESS. PLAY MORE.” is it?

At this point, without Frame Rate Independent Movement or some kind of access to the Box2D time step, the Corona physics engine is just a toy with severe limitations on what can be achieved.

Design accordingly. [import]uid: 40137 topic_id: 17023 reply_id: 65035[/import]

>> Yes, but that’s not exactly “CODE LESS. PLAY MORE.” is it?

:smiley: but it’s great fun! Much more then playing games :slight_smile: Play with code is true fun! :slight_smile: [import]uid: 12704 topic_id: 17023 reply_id: 65038[/import]

>> :smiley: but it’s great fun! Much more then playing games :slight_smile: Play with code is true fun! :slight_smile:

yes, however it’s sort of apples and oranges here. Corona is just a simplified collection of APIs which we can access by scripting in Lua. However, it’s not a true “language” in which we are truly free to code and test things out, without still being limited in many ways.

I like the whole “code less play more” mantra, and agree with it. However, that also means that 200 hours of “playing with code” within this closed system (Lua) can’t really have the same type of impact as a change to the underlying abstracted code. And it’s slightly disingenuous to imply otherwise. [import]uid: 49447 topic_id: 17023 reply_id: 65050[/import]