Physics with an image!

I had an idea of creating an old tanks game using destructable terrain, one using an image, the other a polygon of vertices. Here is the concept of using an image and getting pixels to recreate a physics ball, on of the more simpler body shapes.


-- destructable img

-- Use this function to perform your initial setup
function setup()
    img = image(WIDTH,HEIGHT)
    setContext(img)
    background(40,40,50)
    pushStyle()
    fill(150,150,150,255)
    rect(0,0,WIDTH,HEIGHT/2)
    popStyle()
    setContext()
    ply = vec2(WIDTH/2,HEIGHT-350)
    vel = vec2()
    rad = 10
    r,g,b,a = 40,40,50,255
    holding = false
    parameter.action("Restart",Clear)
end

function touched(t)
    if t.state == BEGAN and vec2(t.x,t.y):dist(ply)>30 then
        r,g,b,a = img:get(t.x,t.y)
    elseif t.state == BEGAN and vec2(t.x,t.y):dist(ply)<30 then
        holding = true
    end
    if holding == false then
    setContext(img)
        pushStyle()
            stroke(r,g,b,a)
            strokeWidth(50)
            line(t.x,t.y,t.prevX,t.prevY)
            fill(r,g,b,a)
            strokeWidth(0)
            ellipse(t.x,t.y,50)
        popStyle()
    setContext()
    else
        ply = vec2(t.x,t.y)
        vel = vec2()
    end
    if t.state == ENDED then
        if holding then holding = false end
    end    
end
function Drag(mlt)
    if mlt > 1 then
        vel = vel-vec2(vel.x,vel.y)/(50*mlt)
    end
end
function Clear()
    setContext(img)
    background(40,40,50)
    pushStyle()
    fill(150,150,150,255)
    rect(0,0,WIDTH,HEIGHT/2)
    popStyle()
    setContext()
end
-- This function gets called once every frame
function draw()
    -- This sets a dark background color 
    background(40, 40, 50,255)

    -- This sets the line thickness
    strokeWidth(5)

    -- Do your drawing here
    sprite(img,WIDTH/2,HEIGHT/2,WIDTH,HEIGHT)
    sprite("Documents:circle",ply.x,ply.y,20,20)
    local nply = ply
    local amnt = 200
    local normal = vec2()
    local n = 0
    local impulse = 0
    local d = false
    for x=1,20 do
        for y=1,20 do
        local r,g,b,a = img:get(ply.x-10+x,ply.y-10+y)
            if vec2(-10+x,-10+y):dist(vec2())<rad then
            if color(r,g,b,255) == color(150,150,150,255) then
                local ov = vel
                --Drag(vec2(-10+x,-10+y):dist(vec2())*5)
                vel = vel + (vec2()-vec2(-10+x,-10+y))/amnt
                normal = normal + (vec2()-vec2(-10+x,-10+y))
                impulse = 10-vec2(-10+x,-10+y):dist(vec2())
                nply = nply + -((vec2(-10+x,-10+y))/450)*impulse
                n = n + 1
                d = true
            end
            end
        end
    end
    normal = normal/n
    if n>0 then
        vel = vel -vec2(vel.x*n,vel.y*n)*0.0001
        ply = nply
    end
    vel = vel + vec2(0,-0.1)

    
    ply = ply + vec2(vel.x,vel.y)
end

I’ve also been thinking about doing a Scorched Earth style tank game; doing sand and destroyable terrain really requires having the ability to peek at the frame buffer.

In the past, I wrote a Similar game for QuickBasic.

The trick was to basically scan each column, from the bottom up, looking at any pixel filled with the “dirt” color. Then look at the pixel underneath. If the pixel underneath was empty, move the pixel down. Then you scan downward from each tank’s base and figure out how far each tank falls.

Let me see if I can work out a little “collapsing terrain” demo. It looks like image.get is the method I need…

Hey welcome to the forum, I know about the collapsing terrain but that would be very performance heavy I think, maybe possible with a shader for better results… The scorched earth is the style I was basing my ideas round but I didn’t think the collapsing part would help as I’m already looping through 305 pixels in the ball.

@tomxp411 - I think you’ll find image.get too slow, if you’re scanning hundreds of pixels per redraw.

What might work better is to use the Minecraft approach and maybe use chunks of pixels, eg 2x2.

Also, Lua is optimised for tables, so if you keep a map of your terrain in memory, it would be fastest to figure out the collapsing by looping through a table, then you could use the results to draw the columns as vertical lines, which is faster than setting individual pixels.

Here is an earlier post when I was trying to do the same for a lemmings type game. I quickly found that a single large image was still too small for my requirements of a big scrollable image so I broke the image into tiles. The code in this discussion deals with this and though adds a sprite instead of removing parts, the principle should be similar

http://twolivesleft.com/Codea/Talk/discussion/1661/creating-a-large-scrollable-image#Item_25

This was where I was thinking towards when I started this one:

http://twolivesleft.com/Codea/Talk/discussion/2077/drop-2000-squares-into-a-pile-at-30-fps#Item_1

I freeze the physics objects when they stop moving to get performance, but my thought was if you destroy an area you could unfreeze the physics objects near it and let them recollapse. Even though the objects are squares which give pleasing bounce/roll effect, you could then draw them as a sprite to make it more “realistic”

very impresive!!

Thanks, Ignatz.

I actually worked up a prototype last night on my Mini,and it’s not that bad. I used a radius of 25 pixels for my “exploded” area, and I am only scanning the area impacted by the explosion. The lag between the end of the explosion and the drop is imperceptible.

Anyway… I’m going to play with this some more. Scorched Earth was a fun game back in the day, and Codea kind of makes me want to write a clone.

I’ll paste some sample code in a follow-up post from the iPad. By golly, I hate that Apple won’t let you export code from the program. Sometimes, the app store rules just leave me truly baffled.

I’ll keep you guys posted on the WIP of “Tanks.” This should be fun.

So here is my prototype. Note that this is just a tech demo, so I didn’t break stuff out in to classes or do things like check/lock the screen orientation… Or even check bounds on my drawing routine. This does demonstrate collapsible terrain, so here goes…

-- Test1

-- Use this function to perform your initial setup
function setup()
    terrainColor=color(174, 174, 174, 255)
    skyColor=color(130,170,255)
    testColor=color(255,255,255)    
    fillTerrain()

    explosionX=-1
    explosionY=-1
    explosionRadius=0

    parameter.action("fillTerrain",fillTerrain)
end

function touched(touch)
    if CurrentTouch.state==ENDED then
        print("touched " .. CurrentTouch.x .. "," .. CurrentTouch.y)
        setExplosion(CurrentTouch.x,CurrentTouch.y,25)
    end
end

function draw()
    strokeWidth(2)
    stroke(255)
    fill(0)
    background(skyColor)

    drawTerrain()
    if explosionRadius > 0 then
        stroke(255,255,0)
        noFill()
        setContext()
        ellipse(explosionX,explosionY,explosionRadius*2)
        explosionRadius = explosionRadius - 1    
   
        if explosionRadius == 0 then     
            dropTerrain(explosionX,explosionY,25)
        end
    end        
end

function setExplosion(x,y,radius)
    explosionRadius=radius
    explosionX=x
    explosionY=y
    makeHole(x,y,radius)
end

function drawTerrain()
    spriteMode(CORNER)
    sprite(terrain,0,0)
end

function fillTerrain()
    print("Filling Terrain")
    terrain=image(WIDTH,HEIGHT)
    setContext(terrain)
    rectMode(CORNER)
    noStroke()
    fill(skyColor)
    rect(0,0,terrain.width,terrain.height)
    fill(terrainColor)
    rect(0,0,terrain.width,terrain.height-300)
    setContext()
end

function makeHole(x,y,radius)
    setContext(terrain)
    noSmooth()
    noStroke()
    fill(skyColor)
    ellipse(x,y,radius*2)
    setContext()
end

function dropTerrain(centerX,centerY,radius)
    setContext(terrain)
    for x=centerX-radius,centerX+radius do
        -- get bottom of opening
        bottom=centerY
        while bottom>=centerY-radius do
            bottom = bottom - 1
            if getColor(terrain,x,bottom) == terrainColor then
                break
            end
        end
        --terrain:set(x,bottom,testColor)

        top=centerY
        while top<=centerY+radius do
            top = top + 1
            if getColor(terrain,x,top) == terrainColor then
                break
            end
        end
        if top > bottom then
            slice=terrain:copy(x,top,2,terrain.height-top)
            spriteMode(CORNER)
            sprite(slice,x,bottom)
        end
    end
    setContext()
end

function getColor(img,x,y)
    r,g,b,a=img:get(x,y)
    return color(r,g,b,a)
end

Nice one@tomxp411

thanks. =)

I’ll post a new thread when I have a playable demo. I don’t want to hijack Luatee’s thread too far off course.