myImage:get(x, y) - Why is it so dann slow?

So I was basically trying to write a simple game where every player around the iPad has its little dot on the map which leaves a long trail. Once you have left the trail nobody can pass it anymore and the player who survives the longest wins. I’m sure you have seen this anywhere else already.

Everything was going fine until I came to the part which is needed to check if the players hit a line or the border or something else. My concept was, that the trails (lines) are drawn to an image of the size of the screen using setContext(). But after coding all that I realised while testing, that the img:get() function slows the whole code down to 35 fps.

So my question is, how can I still check if the player hits a line without that slowdown? When I started the project I also thought of storing all the line parts in a table, but that would slow everything down even quicker, as I would need to check Ever line every frame for every player. And every player (should) leave approximately 60 line parts per second.

Any concept is welcome.

I think the table is going to be way faster. At 60 frames per second, you may find that players are moving less than 1 pixel per frame, so you only need to check one table entry per player (ie the pixel the player is moving into)

Per-pixel operations on modern GPUs are SLLLLLLLOOOOOOOWWWWWWWW.

Like terribad, avoid at all costs, slow. This is the reason people use bounding volumes for collision detections. If you absolutely need pixel perfect accuracy, narrow your tests down as much as possible before testing, and cache as much as you can logically cache.

Put in simplest terms, almost any approach that involves NOT reading pixels directly will most likely be faster, much faster, including the way you described. The most likely answer is bounding volumes, be it circles or squares if your shapes allow it, or closed polygons if not.

Saving the x,y position in a table isn’t going to work by itself because there is a large gap between points as the touch function gets the information.

Yep, in general you want your program to have some internal representation of everything it needs to know. Graphics should only be an output generated from that internal representation. Using the output graphics for logic is generally a very bad thing.

There are then many ways to optimise how you do things like collision detection such as bounding boxes and tree based searches with smaller bounding boxes as you go down the tree and all sorts. Nothing simple, but it’s how it probably needs to be done. Another option in Codea to consider is using the physics system, even if your objects are static, as it has collision detection built in…

@TheMcSebi Are you still working on this. I have it written for 4 players. If you want it, I can post it here or I can send it to you thru PM so you can improve on it without anyone else seeing the code until you’re ready to show it. If not, then I’ll post it as another one of my starter games.

I’d greatly appreciate it if you could post your code here :slight_smile: I have been busy the past couple of days so I haven’t had much time to think about all that. But now I have more time again and I can put more work into that.

I don’t know how to work with bounding boxes but I’ll probably read myself into that soon, as this seems to be a good way of handling collisions. I didn’t use the built in physics api because I was afraid that it would get too slow too quick, as the players move on. I didn’t think that getting 3 pixel colors from a small image would be even slower^^

@TheMcSebi I posted the code under the discussion “started game 14”. I did that just to make it easier for me to keep track of it. You can take what you want from it or add to it to make it better.

Thank you :slight_smile:

As far as i can see you also used the img:get() method to check for hits, so similar to what i had in mind. My concept looks a bit different though :slight_smile:

When I find out how to put code in a fancy box here I’m also post my code from before.

@TheMcSebi I tried doing it different ways, (tables with and without physics objects), but this way was the easiest. The frame rate is low, but it’s the same for all the players so it doesn’t really matter. I tried running this on my iPad 1 and the players still moved at a fast enough rate.

Yes, when using your concept this may not be something which really disturbs the game flow, but as you will be able to see, that doesn’t really match my project…

-- Line

-- Use this function to perform your initial setup
function setup()
    -- Game Settings
    Players = 1
    Speed = 2.5
    -- Game Settings
    
    parameter.watch("getFps()")
    
    gameState = 0
    
    player = {}
    touches = {}
    
    colorTable = {color(255, 0, 0, 255), color(0, 255, 0, 255), color(0, 0, 255, 255), 
                  color(255, 255, 0, 255), color(0, 255, 255, 255), color(255, 0, 255, 255)}
    
    p = {}
    p.spawnBorderDistance = 80
    p.initialThickness = 17.5
    
    g = {}
    g.countdownDuration = 4
    g.controlSpeed = 3.5
    g.outerScreenSpace = 100
    g.mapSizePosCorrection = vec2(g.outerScreenSpace/2, g.outerScreenSpace/2)
    
    for i = 1, Players do
        local initialColor = colorTable[i]
        local initialDirection = math.random(0, 360)
        --local initialDirection = 0
        local initialPosition = vec2(math.random(p.spawnBorderDistance, WIDTH-p.spawnBorderDistance),
                                     math.random(p.spawnBorderDistance, HEIGHT-p.spawnBorderDistance))
        --local initialPosition = vec2(WIDTH/2, HEIGHT/2)
        local initialThickness = p.initialThickness
        local initialInvincibility = 3
        
        table.insert(player, {col = initialColor, dir = initialDirection, pos = initialPosition,
                              prevPos = initialPosition, thick = initialThickness, alive = true,
                              inv = initialInvincibility})
    end
    
    
    map = image(WIDTH+g.outerScreenSpace, HEIGHT+g.outerScreenSpace)
    setContext(map)
    fill(0, 0, 0, 255)
    rect(-g.outerScreenSpace/2, -g.outerScreenSpace/2, WIDTH+g.outerScreenSpace, HEIGHT+g.outerScreenSpace)
    setContext()
    
    counter1 = 0  -- couldnt think of anything else
    counter2 = 0
end

-- process touches
function touched(touch)
    if touch.state == ENDED then
        touches[touch.id] = nil
    else
        touches[touch.id] = touch
    end
end


function draw()
    background(0, 0, 0, 255)
    fill(0, 0, 0, 255)
    
    counter1 = counter1 + 1
    if counter1 > 2 then counter1 = 0 end 
    
    if gameState == 0 then
        -- Menu
        resetStyle()
        --noStroke()
        fill(127, 127, 127, 255)
        ellipse(WIDTH/2, HEIGHT/3, 300)
        
        -- check if button is touched
        for k,touch in pairs(touches) do
            local pos = vec2(touch.x, touch.y)
            if pos:dist(vec2(WIDTH/2, HEIGHT/3)) < 150 then
                g.countdown = g.countdownDuration-0.01 --smooth countdown display
                gameState = 1
            end
        end
    end
    if gameState == 1 then
        -- draw map background
        sprite(map, WIDTH/2, HEIGHT/2)
        fill(0, 0, 0, 0)
        strokeWidth(10)
        stroke(255, 255, 255, 255)
        rect(-5, -5, WIDTH+10, HEIGHT+10)
        
        
        if not (g.countdown < 0) then
            fill(222, 222, 222, 255)
            font("HelveticaNeue-Light")
            fontSize(40)
            text(math.floor(g.countdown), WIDTH/2, HEIGHT-30)
            g.countdown = g.countdown - DeltaTime
        end
        if g.countdown < 1 then
            drawPlayers(false, true)
            updatePlayers()
        else
            drawPlayers(true, false)
        end
        
        local continue = nil
        for i,pl in pairs(player) do
            if pl.alive then
                continue = true
                break
            end
        end
        if not continue then
            gameState = 3
        end
    end
    if gameState == 2 then
        -- Ended
    end
end

function drawPlayers(showDirections, drawLine)
    for i,pl in pairs(player) do
        local pos = pl.pos
        local oldPos = pl.prevPos
        local d = pl.dir
        local c = pl.col
        local t = pl.thick
        
        noStroke()
        fill(c)
        ellipse(pos.x, pos.y, t)
        
        if showDirections then
            resetMatrix()
            translate(pos.x, pos.y)
            rotate(-d)
            stroke(c)
            strokeWidth(6)
            line(0, 0, 0, t*2.3)
            line(0, t*2.3, -t/1.5, t*1.3)
            line(0, t*2.3, t/1.5, t*1.3)
        end
        
        if drawLine then
            if not (pl.inv > 0) then -- check if player doesn't have invincibility tag
                
                -- draw line to map
                resetMatrix()
                setContext(map)
                lineCapMode(SQUARE)
                stroke(c)
                strokeWidth(t-2)
                local lineOldPos = oldPos + vecPos(d-180, 2) + g.mapSizePosCorrection
                local lineNewPos = pos + vecPos(d, 2) + g.mapSizePosCorrection
                line(lineOldPos.x, lineOldPos.y, lineNewPos.x, lineNewPos.y)
                setContext()
            end
        end
    end
end

function updatePlayers()
    for i,pl in pairs(player) do
        if pl.alive then
            for k,touch in pairs(touches) do
                if touch.x < WIDTH/2 then
                    player[i].dir = player[i].dir - g.controlSpeed
                    else
                    player[i].dir = player[i].dir + g.controlSpeed
                end
            end
            
            local dir = pl.dir
            local pos = pl.pos
            
            if pl.inv > 0 then -- update invincibility time
                player[i].inv = pl.inv - DeltaTime
            end
            
            player[i].prevPos = pos
            local acc = vecPos(dir, Speed)
            player[i].pos = pos + acc
        end
    end
    
    checkCollisions() -- this is where it comes to the really slow part
end

function checkCollisions()
    for i,pl in pairs(player) do -- perform func for all players
        if pl.alive then
            for angle = -70, 70, 70 do
                local position = pl.pos -- position.
                local direction = pl.dir -- direction, the player is looking (moving)
                local thickness = pl.thick -- thickness of the player
                
                
                
                local checkPos = position + vecPos(direction+angle, thickness/1.3) + g.mapSizePosCorrection --last is just a constant, don't mind
                red, green, blue = map:get(checkPos.x, checkPos.y) -- get color of the map at the specific point
                local checksum = red+green+blue -- add all colors together
                
                if checksum > 0 then -- see if any of the colors were above 0, -> NOT black
                    player[i].alive = false -- kill player
                end
            end
        end
    end
end


-- helping functions that are not game specific
-- i wrote these a year ago or something...
function getFps()
    if frames == nil then frames = {} end
    table.insert(frames, 1/DeltaTime)
    if #frames > 60 then
        table.remove(frames, 1)
    end
    local framesSum = 0
    for i = 1, #frames do
        framesSum = framesSum + frames[i]
    end
    local fpsCalc = math.floor(framesSum/#frames)
    
    return fpsCalc
end


function vecPos(vec, dist)
    -- Calculate Vector Data
    local x = dist * math.sin(math.rad(vec))
    local y = dist * math.cos(math.rad(vec))
    local v = vec2(x, y)
    return v
end

This was indeed the easiest way, but also the worst, in my case^^

for a project i am working on i would like to count the number of camera pixels above a certain light intensity. I tried using image:get on each pixel but it is quite slow.

Is there a fast way to just count pixels in an image above a certain threshold, perhaps with a shader, or buffers or something else?

@piinthesky - short answer is no

shaders cannot return values, only coloured pixels (which would have to be read with image:get).

I think your best bet is sampling, ie looking at the pixel in every Nth row and Nth column, eg 1 in every 10

Maybe also don’t run the test every frame, because the picture is unlikely to change much in 1/60 of a second.

So if you checked every 10th pixel across and down, just 3 times a second, that would make your program 2,000 times faster.