Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

Struggling to get x and y values for multiple touches in an update draw() frame.

AlkAlk
edited February 2013 in Questions Posts: 5

Hello, I've had a quick glance at the multi-touch demo and I'm using that and the Codea documentation to get constantly updating position information on 2 simultaneous touches (including the drags for these touches).

I'm having trouble though.

As suggested by the example projects, I have a "touches" array to store multiple touches, I have an extra 2 arrays for storing the X and Y position of each touch, and I have a variable which tells me how many touches are currently going on:

touches = {}
xpos = {0,0}
ypos = {0,0}
numtouches = 0

My touch function looks like this:

function touched(touch)
    if touch.state == ENDED then
    touches[touch.id] = nil
    numtouches = numtouches - 1
elseif touch.state == 0 then
    touches[touch.id] = touch
    numtouches = numtouches + 1
end

if touch.state == 1 then
    touches[touch.id] = touch
end

end

In my main draw() update function I have this loop (which I admit I don't fully understand)

for k,touch in pairs(touches) do
    xpos[numtouches] = touch.x
    ypos[numtouches] = touch.y
end

Of course, the issue I'm having is that while the X and Y positions of each touch do get recorded initially, I no longer get a live update of the first touches' X and Y position once the second simultaneous touch occurs, I'm trying to find a way of getting the updated x and y position of ALL simultaneous touch \ drags into their respective xpos and ypos arrays in the update loop but just can't figure out how to do this, I guess this is primarily because I don't quite understand how the for loop works to begin with.

Can anyone help me here?

Tagged:

Comments

  • dave1707dave1707 Mod
    Posts: 8,192

    .. @Alk I posted this a long time ago, maybe this will help you. Move 1 finger around the screen, then another and another. Lift a finger to end the touch, touch again to add more table entries.


    supportedOrientations(PORTRAIT) displayMode(FULLSCREEN) function setup()     mt={}    -- touch table end function draw()     background(40, 40, 50)     fontSize(12)     textMode(CORNER)     text("ID                           STATE                   X            Y",100,1000)         fill(255)     for z=1,#mt do         str=string.format("%9d",mt[z].z)         text(str,60,1000-15*z)    -- touch id         if mt[z].a==0 then             str="BEGAN"         elseif mt[z].a==1 then             str="MOVING"         elseif mt[z].a==2 then             str="ENDED"         else             str="UNKNOWN"         end         text(str,200,1000-15*z)     -- touch state         text(mt[z].x,300,1000-15*z)    -- touch x         text(mt[z].y,350,1000-15*z)    -- touch y     end      end function touched(t)     for z=1,#mt do    -- check for id         if t.id==mt[z].z then    -- id already in table             mt[z]=vec4(t.x,t.y,t.id,t.state) -- update entries                          return         end     end     -- id doesn't match, add new entry to table     table.insert(mt,vec4(t.x,t.y,t.id,t.state)) end
  • Posts: 2,161

    (This is untested ... )

    Try:

    xpos = {}
    ypos = {}
    for k,touch in pairs(touches) do
        table.insert(xpos,touch.x)
        table.insert(ypos,touch.y)
    end
    

    Your code is only changing one entry in the xpos/ypos arrays: the last one. The above builds a new array each time with the current data.

  • AlkAlk
    edited February 2013 Posts: 5

    Firstly - Thank you both for your replies!

    Dave,

    I appreciate your generous lending of code, that said I'd really like to understand what's going on under the hood before I copy and paste someone's work - I've given your code a quick glance this morning but got lost eventually, I will check it again later when I have time.

    One thing I couldn't help notice is that it looks as if at times you directly grab the x value from touch entries in your table - for example mt[z].x this is really interesting because that was my first approach initially using touches[1].x , touches[2].x etc but this asserts for me.

    Andrew,

    I've given your code a go but it didn't work, there were nil values in my xpos and ypos tables, that said I'm not sure I quite understand how your code would have worked - wouldn't it have continually added more table entries rather than overwrite existing ones?

  • edited February 2013 Posts: 455

    Hi Alk,

    There are a couple of issues with your code. Firstly, you only update xpos/ypos[numtouches] so this always will only update your highest valued xpos/ypos.

    Secondly, you actually update the highest valued xpos/ypos with ALL touches values, which while it may seem like the latest touch going in there, may not always be so, because touch.id could be anything, and won't necessarily sequence as you expect.

    To fix this you could reference the xpos/ypos position in your touches array, tweaked code as below (untested, done on my PC)

    function setup()
        touches = {}
        xpos = {}
        ypos = {}
        numtouches = 0
    end
    
    function touched(touch)
        if touch.state == ENDED then
            --cleanup xpos and ypos and renumber later touches....
            local removedTouch = touches[touch.id].numtouch
            table.remove(xpos, removedTouch)
            table.remove(ypos, removedTouch)
            for k,v in pairs(touches) do
                if v.numtouch > removedTouch then
                    v.numtouch = v.numtouch - 1
                end
            end
            touches[touch.id] = nil
            numtouches = numtouches - 1
        elseif touch.state == 0 then
            numtouches = numtouches + 1
            touches[touch.id] = {numtouch = numtouches, touch = touch }
        end
    
        if touch.state == 1 then
            touches[touch.id].touch = touch
        end
    end
    function draw()
        -- This sets a dark background color 
        background(40, 40, 50)
    
        -- This sets the line thickness
        strokeWidth(5)
        clearOutput()
        for k,touch in pairs(touches) do
            xpos[touch.numtouch] = touch.touch.x
            ypos[touch.numtouch] = touch.touch.y
        end
    
    
        for i=1, numtouches do
            print(i..":("..xpos[i]..","..ypos[i]..")")
        end
        -- Do your drawing here
    
    end
    
    

    Hope that makes sense.

    Edit: Andrew_Stacey's post points out a gap in my initial minimal version... now fixed above, I had to add something on a touch end to clear down xpos/ypos appropriately and renumber touches. Tweaked to work on a real device and use output window to track.

    To be honest, I wouldn't use a seperate array and just consume the touches table directly, this reduces a lot of the complexity.

  • Posts: 2,161

    I did say it was untested!

    Handling multi-touches is somewhat complicated so it is best to be absolutely clear in your mind as to what behaviour you want. The basic question to answer is the following: if I do the following:

    1. Start a touch (A)
    2. Start a second touch (B)
    3. Stop the first touch (A)
    4. Stop the second touch (B)

    then how should your array of positions behave? The point is that if you try to keep track of which coordinates corresponded to which touch, your array data will correspond as follows (after each of the above events):

    1. (A)
    2. (A,B)
    3. (nil,B)
    4. (nil,nil)

    However, if you want to ensure that your array is numerically indexed (so is suitable for iterating over using ipairs) then your data will correspond to:

    1. (A)
    2. (A,B)
    3. (B).
    4. empty

    so you lose the continuity of the touch data: the first item in the array of positions flips from A to B. In fact, as pairs is not guaranteed to return its data in any particular order, you might get the data as:

    1. (A)
    2. (B,A)
    3. (B)
    4. empty

    There are various ways to handle touches to avoid such issues, but they basically involve tying the position data more closely to the touch. For an extreme example, you could look at my Touches library (http://loopspace.mathforge.org/discussion/10/touch-tutorial).

    Let's take a less extreme position for now. Let's assume that you just want an array of all the current touch positions, but don't care which position corresponds to which touch. So in setup, we initialise an array of touches.

    function setup()
        touches = {}
    end
    

    In the touched function, we need to capture the touch data. For the moment, we're working on the assumption that we just want the position data and don't care about anything else. In which case, we actually just have to know when to remove a touch from the array.

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

    Now in the draw function we gather the positional data. At this point, some of the data in the touches array has been updated but some might not (if a touch doesn't move, for example) so it's safest just to chuck away all the old positional data and rebuild the arrays. The first line does the "chuck away".

    function draw()
        local xpos,ypos = {},{}
        for _,t in pairs(touches) do
            table.insert(xpos,t.x)
            table.insert(ypos,t.y)
        end
    end
    

    To check this, we should add some output routine.

    Full code:

    function setup()
        touches = {}
    end
    
    function draw()
        local xpos,ypos = {},{}
        for _,t in pairs(touches) do
            table.insert(xpos,t.x)
            table.insert(ypos,t.y)
        end
        background(67,67,67,255)
        noStroke()
        fill(0,255,255,127)
        for k,v in ipairs(xpos) do
            ellipse(v,ypos[k],100)
        end
    end
    
    function touched(touch)
        if touch.state == ENDED then
            touches[touch.id] = nil
        else
            touches[touch.id] = touch
        end
    end
    
  • dave1707dave1707 Mod
    Posts: 8,192

    .. @Alk Even though you got a good response from other people, I'll try to explain what I did with my code. In the setup() routine, I defined a table for my touch information mt={}. In the touched(t) routine, I use a for loop to go through each entry of the table to see if the touch ID is already in the table. If it is, I update the x,y,id,state values with the current data. The ID isn't different since it matched, but since I'm using a vec4 to save the data in the table, I update it anyways. You can have different tables for the different information you want to save, but since I'm only saving 4 values, I used the vec4(). If the ID doesn't match an ID in the table, I create a new entry with all of the data. That's pretty much it, if the ID is in the table, save the new data, if it's not, create a new entry. In the draw() routine, all I'm doing is displaying the information that I saved in the table to show what's happening. Normally when a touch ends, you would remove that entry from the table. I leave it in the table here to show how new touches sometimes create a new ID and sometimes they use an ID that was used before. I don't know how the ID value is calculated, but an ID is valid only from the time you touch the screen (BEGAN) until you lift your finger (ENDED). In my example you can see new ID's being created and sometimes old ID's being re-used. If you have any questions about my code, just ask.

  • AlkAlk
    Posts: 5

    Thanks for all the responses everyone, I'm in the process of going through all the replies and trying to make sense of it all, will hopefully post again in a bit once Ive got some results!

  • AlkAlk
    Posts: 5

    Quick question - am I right in thinking that as long as you hold \ drag a finger on or across the screen the "touched" function is being called every frame?

  • Posts: 2,161

    The touched function is called once per new touch event. It can be that there are no touch events between frames even if some touches are "active". Under normal circumstances, I'd say that dragging your finger should trigger a touch event every frame, but I've also known situations where it hasn't so it can't be relied upon.

    In my touch code, I use the touched routine simply to gather the data. All my processing stuff goes at the beginning of the draw routine. That way I can ensure that the processing is done, and is only done once.

  • dave1707dave1707 Mod
    Posts: 8,192

    Maybe this will show what happens. Touch the screen and move your finger around. Stop moving your finger but don't lift it. Keep doing that and you will see that the draw count always increments, but the touch count only increments when your finger is moving. The draw function gets called constantly, but the touch function only gets called when a touch event happens as @Andrew_Stacey explained.


    function setup()     w=WIDTH/2     h=HEIGHT     x=0     y=0     touchCount=0     drawCount=0 end function draw()     background(40, 40, 50)     drawCount = drawCount + 1     fill(255)     text("x="..x,w,h-100)     text("y="..y,w,h-150)     text("touch count="..touchCount,w,h-200)     text("draw count="..drawCount,w,h-250) end function touched(t)     x=t.x     y=t.y     touchCount = touchCount + 1  end
  • AlkAlk
    Posts: 5

    Hello, apologies for the delayed response, I hadn't got as much time on this as I would have liked!

    I've really struggled with this, and in the end settled on the following code.

    function touched(touch) 
        for i=1,#touches do
            if touch.id == touches[i][1] then
                touches[i] = vec4(touch.id,touch.state,touch.x,touch.y)
                if touches[i][2] == ENDED then
                    touches[i] = nil
                end
                return
            end
        end
        table.insert(touches,vec4(touch.id,touch.state, touch.x,touch.y))
    end
    

    For the most part it works, I can use parameter.watch("touches[1]") (etc) to track my touches updating in real time.

    The problem comes when removing touch entries from the table as I'm sure you can probably already see.

    Currently releasing the second touch before the first works fine, but releasing the first before the second causes a crash, I'm going to assume this is because a nil value can't exist in a table before valid data - I tried to rectify this myself by looking out for the situation and then copying touches[2] into touches[1] and then removing touches[2] but I've had no luck getting this to work.

    Any ideas?

  • dave1707dave1707 Mod
    edited February 2013 Posts: 8,192

    .@Alk You should probably use table.remove to remove entries from a table. You can check the documentation on the use of table.remove, but it would be table.remove(touches,i) instead of touches[i] = nil. Normally I only do one table.remove at a time in a for loop because sometimes the loop gets messed up. A loop using pairs doesn't seem to have a problem with multiple table.remove calls.

  • Posts: 2,161

    Don't remove from a table when iterating over it. That can mess things up.

  • Back to start!

    Alk, your initial code wasn't too bad at all. However, let's make some corrections in the touched function. One is indentation, the other is the use of names instead of numbers:


    function touched(touch) if touch.state == ENDED then touches[touch.id] = nil numtouches = numtouches - 1 elseif touch.state == BEGAN then touches[touch.id] = touch numtouches = numtouches + 1 end if touch.state == MOVING then touches[touch.id] = touch end end

    I advise you to always use proper indentation so you (and we) can better see the structure of the code.

    Well, I just prettified the function, I didn't change what it's doing and in fact it's doing the right things. I think touched was correct all the time.

    Let's look at the loop which - in your own words - you don't fully understand:


    for k,touch in pairs(touches) do xpos[numtouches] = touch.x ypos[numtouches] = touch.y end

    Some rubber duck debugging: What is numtouches? How does numtouches behave in the loop?

    I hope you realized by talking to the rubber duck that the numtouches doesn't have the intended property that you wished it had. If you have no clue then ask here and we'll answer.

    Now let's talk about a problem. You make a touch. I assume you want to keep track of this touch in the first entries of xpos and ypos. Now you make a second touch and you want to keep it in the respective second entries of x/ypos. Now you release the first touch. What do you do with x/ypos? Are you going to delete the first element and the former second becomes the new first one? You may answer "yes", stating that the separate list of positions is ... well ... unordered. They have no real connection to the touches because the second touch suddenly is stored in the first element of the list. Of course, this is legal if you're not doing anything meaningful with the values, you're still trying to figure out what they're doing.

    Forget the last paragraph, I probably only managed to confuse you. Let me ask a better question: Why are you keeping separate lists x/ypos if the information is already stored in the touch stucture? Is it because you want to number your things starting always at 1? Maybe you shouldn't.

    But let's assume you have a good reason to do so. You want your touches to be reflected by a list of positions that are nicely ordered, means they count from 1 and increase by one. Now something happens to a touch? To what touch? Ask your rubber duck: A touch has changed its position, how do I know what touch changed the position? What's unique about an ongoing touch?

    When you have answered this question you have to make a link. Every element of your own x/ypos lists corresponds to a unique touch. You have to remember this. If a touch moves you have to find the element in the list that links to the touch. If a touch ends you have to find the element and remove it.

    I don't know if I can leave this as a homework for you. If you have too much problems I advise you to provide us a small but complete example program since I don't know what you're using x/ypos for.

Sign In or Register to comment.