2D Box 2D Shadows (Update #2)

Hello,
I just wanted to try this out and see if it worked, and it did, pretty well:

displayMode(FULLSCREEN)

function setup()
    props = {}
    table.insert(props,physics.body(CHAIN,loop,
                                    vec2(0,0),
                                    vec2(WIDTH,0),
                                    vec2(WIDTH,HEIGHT),
                                    vec2(0,HEIGHT),
                                    vec2(0,0)
                                ))

    for i = 1,200 do
        local box = physics.body(POLYGON,vec2(-10,-10),vec2(-10,10),vec2(10,10),vec2(10,-10))
        box.type = STATIC
        box.restitution = .5
        box.x = math.random()*WIDTH
        box.y = math.random()*HEIGHT
        table.insert(props,box)
    end
    
    player = physics.body(CIRCLE,10)
    player.x = math.random()*WIDTH
    player.y = math.random()*HEIGHT
    player.sleepingAllowed = false
    
    lightMesh = mesh()
    lightMesh:setColors(255,255,255,255)
end

function draw()
    background(0, 0, 0, 255)
    
    physics.gravity(Gravity)
    
    for i,v in ipairs(props) do
        --drawBody(v)
    end
    pushStyle()
    strokeWidth(3)
    local res = 3
    lightVertices = {}
    for i = 1, 360*res do
        local endPos = vec2(
            player.x+math.cos(math.rad(i/res))*WIDTH,
            player.y+math.sin(math.rad(i/res))*HEIGHT)
        local raycast = physics.raycast(vec2(player.x,player.y),endPos)
        if raycast then
            table.insert(lightVertices,raycast.point)
        end
    end
    
    local lightFan = {}
    for i = 1,#lightVertices do
        local currentObj = lightVertices[i]
        local neighborObj = lightVertices[(i % #lightVertices)+1]
        table.insert(lightFan,player.position)
        table.insert(lightFan,currentObj)
        table.insert(lightFan,neighborObj)
    end
    
    lightMesh.vertices = lightFan
    lightMesh:draw()
    ellipse(player.x,player.y,player.radius*2)
    popStyle()
end

function drawBody(body)
    pushMatrix()
    translate(body.x, body.y)
    rotate(body.angle)
    
    if body.type == STATIC then
        stroke(255,255,255,255)
    elseif body.type == DYNAMIC then
        stroke(150,255,150,255)
    elseif body.type == KINEMATIC then
        stroke(150,150,255,255)
    end
    
    if body.shapeType == POLYGON then
        strokeWidth(3.0)
        local points = body.points
        for j = 1,#points do
            a = points[j]
            b = points[(j % #points)+1]
            line(a.x, a.y, b.x, b.y)
        end
    elseif body.shapeType == CHAIN or body.shapeType == EDGE then
        strokeWidth(3.0)
        local points = body.points
        for j = 1,#points-1 do
            a = points[j]
            b = points[j+1]
            line(a.x, a.y, b.x, b.y)
        end      
    elseif body.shapeType == CIRCLE then
        strokeWidth(3.0)
        line(0,0,body.radius-3,0)            
        ellipse(0,0,body.radius*2)
    end
        
    popMatrix()
end

Wow that’s brilliant! This stuff i can play around with for ages

Add

supportedOrientations(CurrentOrientation)

at the top to prevent the screen flipping when you try to roll the ball.

Great work @Zoyt, that’s a really cool effect.

@Luatee, @Simeon - Thanks a lot.
New version. In this version, you can tap to add lights. Tap again to make brighter. Double tap and keep tapping continuously to make light dimmer. Also, everything is in functions so you can easily include it in your own code. Here it is:

displayMode(FULLSCREEN)

function setup()
    props = {}
    table.insert(props,physics.body(CHAIN,loop,
                                    vec2(0,0),
                                    vec2(WIDTH,0),
                                    vec2(WIDTH,HEIGHT),
                                    vec2(0,HEIGHT),
                                    vec2(0,0)
                                ))

    for i = 1,50 do
        local box = physics.body(POLYGON,vec2(-10,-10),vec2(-10,10),vec2(10,10),vec2(10,-10))
        box.type = STATIC
        box.restitution = .5
        box.x = math.random()*WIDTH
        box.y = math.random()*HEIGHT
        table.insert(props,box)
    end
    
    tUsedId = nil
    
    lights = {}
end

function draw()
    background(0, 0, 0, 255)
    
    physics.gravity(Gravity)
    
    for i,v in ipairs(props) do
        --drawBody(v)
    end
    
    for i,v in ipairs(lights) do
        drawLight(v,3)
    end
end

function drawBody(body)
    pushMatrix()
    translate(body.x, body.y)
    rotate(body.angle)
    
    if body.type == STATIC then
        stroke(255,255,255,255)
    elseif body.type == DYNAMIC then
        stroke(150,255,150,255)
    elseif body.type == KINEMATIC then
        stroke(150,150,255,255)
    end
    
    if body.shapeType == POLYGON then
        strokeWidth(3.0)
        local points = body.points
        for j = 1,#points do
            a = points[j]
            b = points[(j % #points)+1]
            line(a.x, a.y, b.x, b.y)
        end
    elseif body.shapeType == CHAIN or body.shapeType == EDGE then
        strokeWidth(3.0)
        local points = body.points
        for j = 1,#points-1 do
            a = points[j]
            b = points[j+1]
            line(a.x, a.y, b.x, b.y)
        end      
    elseif body.shapeType == CIRCLE then
        strokeWidth(3.0)
        line(0,0,body.radius-3,0)            
        ellipse(0,0,body.radius*2)
    end
        
    popMatrix()
end

function touched(t)
    if t.state == 0 and t.id ~= tUsedId then
        tUsedId = t.id
        local used = false
        for i = #lights, 1, -1 do
            v = lights[i]
            if t.tapCount == 2 then
                v.color.a = v.color.a - 30
                used = true
            elseif t.tapCount >= 3 then
                v.color.a = v.color.a - 10
                used = true
            elseif v.pos:dist(vec2(t.x,t.y)) < 30 then
                v.color.a = v.color.a + 20
                used = true
            end
            if v.color.a <= 0 then
                table.remove(lights,i)
            end
        end
        if not used then
            table.insert(lights,getLight(t.x,t.y))
        end
    end
end

function getLight(x,y)
    local l = {}
    l.m = mesh()
    l.m:setColors(255,255,255,255)
    l.pos = vec2(x,y)
    l.color = color(255, 255, 255, 20)
    return l
end

function drawLight(l,res)
    local lightVertices = {}
    for i = 1, 360*res do
        local endPos = vec2(
            l.pos.x+math.cos(math.rad(i/res))*WIDTH,
            l.pos.y+math.sin(math.rad(i/res))*HEIGHT)
        local raycast = physics.raycast(l.pos,endPos)
        if raycast then
            table.insert(lightVertices,raycast.point)
        end
    end
    
    local lightFan = {}
    for i = 1,#lightVertices do
        local currentObj = lightVertices[i]
        local neighborObj = lightVertices[(i % #lightVertices)+1]
        table.insert(lightFan,l.pos)
        table.insert(lightFan,currentObj)
        table.insert(lightFan,neighborObj)
    end
    
    l.m.vertices = lightFan
    
    pushStyle()
    fill(l.color)
    l.m:draw()
    fill(127, 127, 127, 255)
    ellipse(l.pos.x,l.pos.y,5)
    popStyle()
end

Thanks!

Arrrgh! Again: cant copy the code! Could you use the simpler format? Thanks.

@Jmv38, I can copy the code off my iPad, maybe is it a problem with your iPad? I just hover my finger over some text (usually near the bottom) and slide it to the right (into empty space) and it selects everything just fine :slight_smile:

Really nice !

For my part, i’v read this article : http://www.redblobgames.com/articles/visibility/

And try to remake the code (https://gist.github.com/HyroVitalyProtago/5939302)

but i have no time to test this for the moment, if you are interested and have time, don’t hesitate :wink:

@HyroVitalyProtago - Interesting article. I’ll try out your remake when I get a chance.

@Jmv38 - Fixed

Thank you @Zoyt.
@jordan it could be a problem with my ios: 5.1. I have ipad1.

@Jmv38 - No problem. This is MUCH heavier OpenGL mesh manipulation (1028 triangles per light), so you might not be able to get it to run.

So you thought the previous update was cool… Just try this one. In this update, the draw function is now within the light object, so you just have to call light:draw(). It also has other various improvements to the function. The biggest thing in this update is the limit of light distance. This makes it look like real lighting. Here it is:

displayMode(FULLSCREEN)

function setup()
    props = {}
    table.insert(props,physics.body(CHAIN,loop,
                                    vec2(0,0),
                                    vec2(WIDTH,0),
                                    vec2(WIDTH,HEIGHT),
                                    vec2(0,HEIGHT),
                                    vec2(0,0)
                                ))

    for i = 1,100 do
        local box = physics.body(POLYGON,vec2(-10,-10),vec2(-10,10),vec2(10,10),vec2(10,-10))
        box.type = STATIC
        box.gravityScale = 0
        box.restitution = .5
        box.x = math.random()*WIDTH
        box.y = math.random()*HEIGHT
        table.insert(props,box)
    end
    for i = 1,10 do
        local circ = physics.body(CIRCLE,10)
        --circ.type = STATIC
        circ.restitution = .5
        circ.x = math.random()*WIDTH
        circ.y = math.random()*HEIGHT
        table.insert(props,circ)
    end
    for i = 1,50 do
        if i % 2 == 0 then
            table.insert(props,physics.body(EDGE,
                                                vec2(WIDTH/2,i*(HEIGHT/5/50)),
                                                vec2(WIDTH/2,(i+1)*(HEIGHT/5/50))
                                            ))
        end
    end
    
    tUsedId = nil
    
    lights = {}
end

function draw()
    background(0, 0, 0, 255)
    
    physics.gravity(Gravity/5)
    
    for i,v in ipairs(lights) do
        v:draw()
    end
end

function drawBody(body)
    pushMatrix()
    translate(body.x, body.y)
    rotate(body.angle)
    
    if body.type == STATIC then
        stroke(255,255,255,255)
    elseif body.type == DYNAMIC then
        stroke(150,255,150,255)
    elseif body.type == KINEMATIC then
        stroke(150,150,255,255)
    end
    
    if body.shapeType == POLYGON then
        strokeWidth(3.0)
        local points = body.points
        for j = 1,#points do
            a = points[j]
            b = points[(j % #points)+1]
            line(a.x, a.y, b.x, b.y)
        end
    elseif body.shapeType == CHAIN or body.shapeType == EDGE then
        strokeWidth(3.0)
        local points = body.points
        for j = 1,#points-1 do
            a = points[j]
            b = points[j+1]
            line(a.x, a.y, b.x, b.y)
        end      
    elseif body.shapeType == CIRCLE then
        strokeWidth(3.0)
        line(0,0,body.radius-3,0)            
        ellipse(0,0,body.radius*2)
    end
        
    popMatrix()
end

function touched(t)
    if t.state == 0 and t.id ~= tUsedId then
        tUsedId = t.id
        local used = false
        for i = #lights, 1, -1 do
            v = lights[i]
            if v.pos:dist(vec2(t.x,t.y)) < 100 and t.tapCount == 2 then
                table.remove(lights,i)
                used = true
            end
        end
        if not used then
            table.insert(lights,getLight(vec2(t.x,t.y),math.random(100,400),3))
        end
    end
end

function getLight(pos,size,res)
    local l = {}
    l.m = mesh()
    l.m:setColors(255,255,255,255)
    
    local img = image(5,5)
    setContext(img)
    pushStyle()
    noStroke()
    fill(255, 255, 255, 255)
    ellipse(img.width/2,img.height/2,img.width)
    popStyle()
    setContext()

    l.m.texture = img
    l.res = res or 2
    l.pos = pos
    l.color = color(255, 255, 255, 121)
    l.size = size or 300
    
    l.draw = function()
            local lightVertices = {}
            for i = 1, 360*l.res do
                local endPos = vec2(
                    l.pos.x+math.cos(math.rad(i/l.res))*l.size*1.75,
                    l.pos.y+math.sin(math.rad(i/l.res))*l.size*1.75)
                local raycast = physics.raycast(l.pos,endPos)
                if raycast then
                    table.insert(lightVertices,raycast.point)
                else
                    table.insert(lightVertices,endPos)
                end
            end
            
            local lightFan = {}
            for i = 1,#lightVertices do
                local currentObj = lightVertices[i]
                local neighborObj = lightVertices[(i % #lightVertices)+1]
                table.insert(lightFan,l.pos)
                table.insert(lightFan,currentObj)
                table.insert(lightFan,neighborObj)
            end
            
            l.m.vertices = lightFan
            l.m.texCoords = l.m.vertices
            l.mapMeshCoords(l.m,l.pos-vec2(l.size,l.size),l.pos+vec2(l.size,l.size))
            
            
            pushStyle()
            fill(l.color)
            l.m:draw()
            popStyle()
        end
    
    l.mapMeshCoords = function( m , min , max )
     
            local bounds = vec2( max.x - min.x, max.y - min.y )
            
            local v, tx, ty
            for i = 1,m.size do
                v = m:vertex(i)
         
                tx = (v.x - min.x) / bounds.x 
                ty = (v.y - min.y) / bounds.y
         
                m:texCoord( i, tx, ty )
            end
        end
        
    return l
end

Whoops. I forgot to add this update to the code above. Now, you have make light without bounds around the arena. The edges of the mesh also only go to the edge of the light now.
I hope it’s useful!