Dynamic 2D water

Based on http://gamedevelopment.tutsplus.com/tutorials/creating-dynamic-2d-water-effects-in-unity--gamedev-14143 which I came across via @andymac3d’s blog I’ve implemented the first bit in Codea. Touch the screen to create a wave.

-- waves
--by West
--based on http://gamedevelopment.tutsplus.com/tutorials/creating-dynamic-2d-water-effects-in-unity--gamedev-14143

-- Use this function to perform your initial setup
function setup()
    numwatersprings=150
    water={}
    border=10
    for i=1,numwatersprings do 
        table.insert(water,{x=border+i*(WIDTH-2*border)/numwatersprings,y=HEIGHT/2,vel=0,acc=0})
    end
    parameter.number("springconstant",0.01,0.1,0.02)
    parameter.number("damping",0.01,0.1,0.04)
    parameter.number("spread",0.01,0.1,0.01)
    parameter.integer("neighbours",5,20,9)

    leftDeltas={}
    rightDeltas={}
    baseheight=HEIGHT/2
    left=100
    bottom=50
end

-- This function gets called once every frame
function draw()
    -- This sets a dark background color 
    background(40, 40, 50)
    -- This sets the line thickness
    strokeWidth(2)

    if CurrentTouch.state==BEGAN then
        local nearestspring=math.floor(numwatersprings*CurrentTouch.x/WIDTH)
        if nearestspring==0 then nearestspring=1 end
        water[nearestspring].y=(CurrentTouch.y)
    end
    --calculate the new position of each water spring element
    for u,w in pairs(water) do
        force = springconstant * (w.y - baseheight) + w.vel*damping
        w.acc = -force
        w.y = w.y + w.vel
        w.vel = w.vel + w.acc
    end
    --let each spring affect its neighbours
    for j=1,neighbours do
        for i=1,numwatersprings do
            if (i > 1) then
                leftDeltas[i] = spread * (water[i].y - water[i-1].y);
                water[i-1].vel = water[i-1].vel + leftDeltas[i];
            end
            if (i < numwatersprings - 1) then            
                rightDeltas[i] = spread * (water[i].y - water[i+1].y);
                water[i+1].vel=water[i+1].vel + rightDeltas[i];
            end
        end
    end

    for i=1,numwatersprings do
        if (i > 1) then    
            water[i-1].y =water[i-1].y+ leftDeltas[i];
        end
        if (i < numwatersprings- 1) then    
            water[i+1].y=water[i+1].y + rightDeltas[i];
        end
    end
    --draw the water line
    for i=1,numwatersprings-1 do
        line(water[i].x,water[i].y,water[i+1].x,water[i+1].y)
    end
end

nice!
With a bit more effort…world of jelly:
http://www.youtube.com/watch?v=qsYs5rY4HvI

Good work @West - lots of potential for this methinks!

P.s. Im a big fan of the tuts+ site - great resource for game-devs and nicely written as well!

Really nice! I made a small change to make it look more like water:

-- waves
--by West
--based on http://gamedevelopment.tutsplus.com/tutorials/creating-dynamic-2d-water-effects-in-unity--gamedev-14143

-- Use this function to perform your initial setup
function setup()
    numwatersprings=150
    water={}
    border=10
    for i=1,numwatersprings do 
        table.insert(water,{x=border+i*(WIDTH-2*border)/numwatersprings,y=HEIGHT/2,vel=0,acc=0})
    end
    parameter.watch("1/DeltaTime")
    parameter.number("springconstant",0.01,0.1,0.02)
    parameter.number("damping",0.01,0.1,0.04)
    parameter.number("spread",0.01,0.1,0.01)
    parameter.integer("neighbours",5,20,9)

    leftDeltas={}
    rightDeltas={}
    baseheight=HEIGHT/2
    left=100
    bottom=50
end

-- This function gets called once every frame
function draw()
    -- This sets a dark background color 
    background(40, 40, 50)
    -- This sets the line thickness
    strokeWidth(2)

    if CurrentTouch.state==BEGAN then
        local nearestspring=math.floor(numwatersprings*CurrentTouch.x/WIDTH)
        if nearestspring==0 then nearestspring=1 end
        water[nearestspring].y=(CurrentTouch.y)
    end
    --calculate the new position of each water spring element
    for u,w in pairs(water) do
        force = springconstant * (w.y - baseheight) + w.vel*damping
        w.acc = -force
        w.y = w.y + w.vel
        w.vel = w.vel + w.acc
    end
    --let each spring affect its neighbours
    for j=1,neighbours do
        for i=1,numwatersprings do
            if (i > 1) then
                leftDeltas[i] = spread * (water[i].y - water[i-1].y);
                water[i-1].vel = water[i-1].vel + leftDeltas[i];
            end
            if (i < numwatersprings - 1) then            
                rightDeltas[i] = spread * (water[i].y - water[i+1].y);
                water[i+1].vel=water[i+1].vel + rightDeltas[i];
            end
        end
    end

    for i=1,numwatersprings do
        if (i > 1) then    
            water[i-1].y =water[i-1].y+ leftDeltas[i];
        end
        if (i < numwatersprings- 1) then    
            water[i+1].y=water[i+1].y + rightDeltas[i];
        end
    end
    --draw the water line
    stroke(0, 150, 255, 255) strokeWidth(6)
    for i = 1, numwatersprings do
        local j = math.min(i + 1, numwatersprings)
        line(water[i].x,water[i].y,water[j].x,water[j].y)
        line(water[i].x, water[i].y, water[i].x, 0)
    end
end

@JakAttak nice - I went a step further and replaced the lines with meshes. I’ve also added rocks to drop into the water and droplets of water

-- waves
--by West
--based on http://gamedevelopment.tutsplus.com/tutorials/creating-dynamic-2d-water-effects-in-unity--gamedev-14143

-- Use this function to perform your initial setup
function setup()
   -- displayMode(FULLSCREEN)
    numwatersprings=60
    water={}
    wverts={}
    droplets={}
    rocks={}
    border=0
    for i=1,numwatersprings do 
        table.insert(water,{x=border+(i-1)*(WIDTH-2*border)/(numwatersprings-1),y=HEIGHT/2,vel=0,acc=0})
    end
    parameter.number("springconstant",0.01,0.1,0.02)
    parameter.number("damping",0.01,0.1,0.06)
    parameter.number("spread",0.01,0.5,0.03)
    parameter.integer("neighbours",5,20,9)

    leftDeltas={}
    rightDeltas={}
    baseheight=HEIGHT/2
    bottom=0
    watermesh=mesh()
end

-- This function gets called once every frame
function draw()
    -- This sets a dark background color 
    background(110, 169, 224, 255)
    -- This sets the line thickness
    strokeWidth(3)

    if CurrentTouch.state==BEGAN then
        table.insert(rocks,{x=CurrentTouch.x,y=CurrentTouch.y,speed=0,splash=0})
    end
    --calculate the new position of each water spring element
    for u,w in pairs(water) do
        force = springconstant * (w.y - baseheight) + w.vel*damping
        w.acc = -force
        w.y = w.y + w.vel
        w.vel = w.vel + w.acc
    end
    --let each spring affect its neighbours
    for j=1,neighbours do
        for i=1,numwatersprings do
            if (i > 1) then
                leftDeltas[i] = spread * (water[i].y - water[i-1].y);
                water[i-1].vel = water[i-1].vel + leftDeltas[i];
            end
            if (i < numwatersprings - 1) then            
                rightDeltas[i] = spread * (water[i].y - water[i+1].y);
                water[i+1].vel=water[i+1].vel + rightDeltas[i];
            end
        end
    end

    for i=1,numwatersprings do
        if (i > 1) then    
            water[i-1].y =water[i-1].y+ leftDeltas[i];
        end
        if (i < numwatersprings- 1) then    
            water[i+1].y=water[i+1].y + rightDeltas[i];
        end
    end
    --draw the water line
    for i=1,numwatersprings-1 do
        fill(50,70,190,255)
        stroke(36, 32, 95, 255)
        line(water[i].x,water[i].y,water[i+1].x,water[i+1].y)
    --draw as a mesh
        wverts[(i-1)*6+5]=vec2(water[i].x,water[i].y)
        wverts[(i-1)*6+3]=vec2(water[i+1].x,water[i+1].y)
        wverts[(i-1)*6+1]=vec2(water[i+1].x,bottom)
        wverts[(i-1)*6+2]=vec2(water[i+1].x,bottom)
        wverts[(i-1)*6+6]=vec2(water[i].x,bottom)
        wverts[(i-1)*6+4]=vec2(water[i].x,water[i].y)       
    end
    
        -- Draw the water droplets
    for s,d in pairs(droplets) do
        --add transparency to the droplets so they fades away
        tint(77, 194, 231, d.fade)
        
        local angle=math.atan2(d.yspeed,d.xspeed*math.sin(math.rad(-d.dir)))
        d.x = d.x + d.xspeed*math.sin(math.rad(-d.dir))
        d.y = d.y + d.yspeed
        d.yspeed = d.yspeed -4

        pushMatrix()
            translate(d.x,d.y)
            rotate(math.deg(angle)+90)
            sprite("Small World:Raindrop Soft",0,0,15,20) 
        popMatrix()

        d.fade = d.fade -5
        if d.fade<0 then
            table.remove(droplets,s)
        end
    end
    tint(255)
    
    watermesh.vertices=triangulate(wverts)
    watermesh:setColors(40,60,180,255)
    watermesh:draw()     
    
    for s,r in pairs(rocks) do
        fall=4
        tint(255)
        if r.y<HEIGHT/2-50 then
            fall=0.5
            tint(40,60,180,100)
        end
        sprite("Tyrian Remastered:Rock 3",r.x,r.y)
        r.speed = r.speed + fall
        r.y = r.y -r.speed
        local splashdepth=2
        if r.splash<splashdepth and r.y<HEIGHT/2 then
            r.splash = r.splash + 1
            dfact=math.floor(r.speed)
            local nearestspring=math.floor(numwatersprings*CurrentTouch.x/WIDTH)
            if nearestspring==0 then nearestspring=1 end
            water[nearestspring+1].y=(r.y)
            water[nearestspring].y=(r.y)
            --add droplets
            if r.splash==splashdepth then
                r.speed = r.speed /10
                for i=1,dfact do
                    table.insert(droplets,
                    {x=r.x,y=HEIGHT/2-dfact,dir=340+math.random(40),
                    fade=200+math.random(50),size=12+math.random(5),xspeed=dfact+math.random(20),yspeed=dfact+math.random(20)})
                end    
            end
        end

    end
end

Edit: typo changed WIDTH to HEIGHT in droplets
Edit: changed the order in which the vertices are processed to the proper order!

Wonderful… thanks a lot. Is it OK to reuse this code in a project?

Is “the floor is jelly” a project of yours developed on Codea alone, @piinthesky ?

@Rodolphe yes no probs from me - I just reimplemented someone else’s work so if giving attributions link back to them in the first instance. Also note that I’ve just edited the code to render the vertices in the correct order - the previous version caused some flickering

@rodolphe, no unfortunately floor of jelly is nothing to do with me. If i remember correctly it is a PC game and not made with codea.

Thanks @West!

Oh ok, @piinthesky, I should have looked further… I was quite impressed by little details like the flags. Is that, the flapping flags, for instance, something we could accomplish with methods similar to what’s used for the 2d water rendering?