Using the SurfaceToTUIO part of squidy-lib to get TUIO apps working on MS Surface
Posted: 11 March 2010 07:08 AM   [ Ignore ]
Rank
Joined  2010-01-18
Total Posts:  31
New Member

We have an application which we test on our own CCV based table which needs to run on a customer MS Surface unit.

As mentioned in a previous post there should be a method of getting TUIO apps to work on MS Surface. This enables apps developed for CCV based equipment to work in an MS Surface environment and gives the advantage to TUIO using developers as there is another platform they can use.

The way this should work is to make use of a part of the squidy-lib library (see squidy-lib.de) called SurfaceToTUIO to get Surface events converted into TUIO ones. However in practice although the SurfaceToTUIO C# code is available and compiles in VS2008 it does not appear to be processing Surface messages. In the Surface simulator it appears to continuously process the one event - on the surface table it does not appear to respond to events at all.

Therefore I was wondering if anyone has had experience with this and what was the outcome. I hope to track the errors down and post the results on this forum.

Although strictly speaking this should go on the squidy-lib forum there has not been a single read of my post on there so this certainly seems to be the more active community!

bamalam

Profile
 
 
Posted: 11 March 2010 07:03 PM   [ Ignore ]   [ # 1 ]
Avatar
Rank
Joined  2009-09-15
Total Posts:  78
New Member

Are you trying to run non-Surface applications on the Surface table?  I’ve done this, and have also made applications that can run on Surface -OR- respond to TUIO data via OSC (eg, CCV).  For my purposes, I sent all incoming packets to a function called “readPacket(OscPacket packet);”.  I also converted all the Surface data to TUIO and sent it to the readPacket function (do not try to send/receive packets via UDP in the same app—it doesn’t work).

To make Surface data available to you, create a “ContactTarget” object and enable it…

Make sure you have these imports…

using Microsoft.Surface;
using Microsoft.Surface.Core;

Declare a contact target object…

private ContactTarget cTargets;

In your initialization routine, enable input like so…

cTargets = new ContactTarget(IntPtr.Zerotrue);
cTargets.EnableInput()

That should make Surface work.  To get touchpoint info, create a “ReadOnlyContactCollection” object at any time.  This will contain info on anything Surface sees.

Assuming you are in XNA, you could do your update like so…

protected override void Update(GameTime gt)
{

    
// Get all contacts
    
ReadOnlyContactCollection contacts cTargets.GetState();

    
// Iterate through list
    
foreach (Contact c in contacts)
    
{

        
// This is a touch blob
        
if (contact.IsFingerRecognized)
        
{

            
// Calculate X, Y
            
double x = (c.Bounds.Right c.Bounds.Left) * 0.5 c.Bounds.Left;
            
double y = (c.Bounds.Top c.Bounds.Bottom) * 0.5 c.Bounds.Top;

            
// Scale to screen
            
1024;
            
768;

            
// Write position
            
System.Diagnostics.Debug.WriteLine(", " y);

        
}

    }

    
// Run update on base class
    
base.Update(gt);

}

And that would give you the coordinates.

If you want to dispatch that data over OSC to another, non-Surface app (thus utilizing the table for your own apps), just pass the contact collections (called “contacts” in this example) to Squidy’s surfaceToTUIO function.

Profile
 
 
Posted: 12 March 2010 12:47 PM   [ Ignore ]   [ # 2 ]
Rank
Joined  2010-01-18
Total Posts:  31
New Member

Thanks for the info Let’s Go outside,

you’ve provided some really useful tips, particularly not having the UDP one on the same app.

The SurfaceToTUIO application at it’s heart uses the same mechanism that you have in the code of your post. What it doesn’t have is the IsFinger... bit which is where I think there might be problems. There is a lot of other stuff in relation to currentContacts and previousContacts that it is difficult to decipher. Here is the bit that is called to generate the messages:

/// <summary>
        /// Here a TUIO 2D cursor message is prepared for each contact and sent along with an Alive message at the begining 
        /// and a Frame message at the end. the messages are then sent as a bundle.
        /// </summary>
        public void sendTUIO_2DCur(List<Contactcontacts)
        
{
            
if (contacts.Count == 0)
                return;

            
OSCBundle cursorBundle = new OSCBundle();

            
//Console.WriteLine(contacts.Count);
            
OSCMessage aliveMessage TUIO_2DCur.aliveMessage(contacts);
            
cursorBundle.Append(aliveMessage);

            for (
int i 0contacts.Counti++)
            
{
                Contact c 
contacts[i];
                
double left c.Bounds.Left;
                
double right c.Bounds.Right;
                
double top c.Bounds.Top;
                
double bottom c.Bounds.Bottom;
                
double x = (right left) / left base.Window.ClientBounds.Left;
                
double y = (top bottom) / bottom base.Window.ClientBounds.Top;
                
Properties.Settings.Default.DISPLAY_W;
                
Properties.Settings.Default.DISPLAY_H;
                
OSCMessage setMessage TUIO_2DCur.setMessage(c.Id, (float)x, (float)y0.0f0.0f0.0f);
                
cursorBundle.Append(setMessage);
            
}

            OSCMessage frameMessage 
TUIO_2DCur.frameMessage(_Frame);
            
cursorBundle.Append(frameMessage);
            
_Frame++;
            
_OSCSender.Send(cursorBundle);
        
}

Note that base.Window.ClientBounds.Left and .Top bits that I added to allow finger contact to be correct if you are using the simulator on a screen larger than the native 1024x768.

I’ll be looking further into it over the next few days because the check is on the debug messages produced by TUIO in the Flash application. There the finger touch is registered alright but the message still appears when you release your finger as if the application is still sending the last finger contact. A new contact is not registered unless you hold it down and move it around at which point the touch is there on the TUIO debug of the flash application following the finger movement.

I also noticed the app use 100% of the CPU it is running on when it is in focus which is troubling.

bamalam

Profile
 
 
Posted: 12 March 2010 01:51 PM   [ Ignore ]   [ # 3 ]
Avatar
Rank
Joined  2009-09-15
Total Posts:  78
New Member
bamalam - 12 March 2010 12:47 PM

Note that base.Window.ClientBounds.Left and .Top bits that I added to allow finger contact to be correct if you are using the simulator on a screen larger than the native 1024x768.

Right, I just looked at one of my apps and am doing the same thing (the code in the previous post was off memory).

bamalam - 12 March 2010 12:47 PM

I’ll be looking further into it over the next few days because the check is on the debug messages produced by TUIO in the Flash application. There the finger touch is registered alright but the message still appears when you release your finger as if the application is still sending the last finger contact. A new contact is not registered unless you hold it down and move it around at which point the touch is there on the TUIO debug of the flash application following the finger movement.

I also noticed the app use 100% of the CPU it is running on when it is in focus which is troubling.
bamalam

With any sort of blob detection, you want to keep track of what is what.  In both Surface and in Flash, you’ll want to keep a collection of blobs and their IDs.  As you iterate through incoming blobs, first check if you’ve added it already.  Use the incoming data to update the existing blob or add it if it isn’t found.  You should also iterate through your list of existing blobs and see if they need to be removed.

In Flash, I like to record the last time a blob was updated.  On every frame, I iterate through the list and remove any blob that hasn’t been updated in, say, 75 milliseconds.  With Surface, it’s ever easier. 

// List of active contacts
private List<ContactactiveContacts;

protected 
override void Update(GameTime gt)
{

    
// Get all contacts
    
ReadOnlyContactCollection contacts cTargets.GetState();

    
// Iterate through list
    
int activeContactCount activeContacts.Count;
    for (
int i 0activeContactCounti++)
    
{

         
// Compare contact in your list with current contacts
         
if (cTargets.TryGetContactFromID(activeContacts[i].Id) == null)
         
{

              
// Remove contact from activeContacts because it is not in the
              // list of currents contacts.  Sorry, I've been in C++ land and forget
              // how to do remove items from lists (usually the copy/overwrite method)

         
}

    }

    
// Iterate through list
    
foreach (Contact c in contacts)
    
{

        
// This is a touch blob
        
if (contact.IsFingerRecognized)
        
{

             
// Create a "found" flag to indicate that we've found an existing contact
             
bool found false;

             
// Iterate through list of active contact
             
activeContactCount activeContacts.Count;
             for (
0activeContactCounti++)
             
{

                  
// This is an existing contact
                  
if (activeContacts[i].Id == contact.Id)
                  
{

                       
// Overwrite contact and set flag
                       
activeContacts[i] contact;
                       
found true;

                  
}

             }

             
// Contact was not found, so this is a new one
             
if (!found)
             
{

                     
// Add "contact" to your activeContacts list here (also forgot how to do this in C#)

             
}

        }

    }

    
// Now that the list is updated, we're ready to send active blobs
    
sendTUIO_2DCur(activeContacts);

    
// Run update on base class
    
base.Update(gt);

}

I’m pretty sure this works (again, writing off the top of my head).  I think Surface returns null for inactive contacts when you use the TryGetContactFromId() method, even though it keeps all blobs in its list, which would indicate to you what needs to be dropped from your active contacts list.

Profile
 
 
Posted: 18 March 2010 04:16 PM   [ Ignore ]   [ # 4 ]
Rank
Joined  2010-01-18
Total Posts:  31
New Member

Made further progress based on the previous post from Let’s Go Outside.

However although now I’m seeing touch points added and removed there is a problem when I hold a touch point and move it around. Although it keeps the ID of the message the touch is blinking on and off as if it is losing some of the alive messages.

In fact I’ve discovered that it sometimes does not pick up on new touch point as well.

Here is the code:

/// <summary>
        /// Allows the app to run logic such as updating the world,
        /// checking for collisions, gathering input and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        
{
            _Inact
++;
            
iterCount++;
            if ( (
isApplicationActivated || isApplicationPreviewed) && (_Inact 0) )
            
{
                _Inact 
0;
                
// Process all the contacts to change, add or remove the ones since the last update.

                // Get a list of the current contacts
                
ReadOnlyContactCollection currentContacts contactTarget.GetState();

                
// Iterate through list
                
int activeContactCount activeContacts.Count;
                for (
int i 0activeContactCounti++)
                
{

                    Contact c 
null;
                    
currentContacts.TryGetContactFromId (activeContacts[i].Idout c);
                    
// Compare contact in your list with current contacts
                    
if ( == null)
                    
{
                        
// Remove contact from activeContacts because it is not in the list of current contacts.  
                        
activeContacts.Remove(activeContacts[i]);
                        
removeCount++;
                    
}
                }

                
foreach (Contact contact in currentContacts)
                
{
                    Contact c 
null;
                    
bool found false;
                    
// Update the count to correct for removed ones.
                    
activeContactCount activeContacts.Count;
                    
// Ignore null contacts
                    
currentContacts.TryGetContactFromId(contact.Idout c);
                    if (
!= null)
                    
{
                        
if (contact.IsFingerRecognized)
                        
{
                            
// Iterate through list of active contact
                            
for (int i 0activeContactCounti++)
                            
{

                                
// This is an existing contact
                                
if (activeContacts[i].Id == contact.Id)
                                
{
                                    
// Overwrite contact and set flag
                                    
activeContacts[i] contact;
                                    
found true;
                                    
_ProcOk true;
                                    
updateCount++;
                                
}
                            }

                            
// Contact was not found, so this is a new one
                            
if (!found)
                            
{
                                
// Add "contact" to your activeContacts list here
                                
activeContacts.Add(contact);
                                
_ProcOk true;
                                
addCount++;
                            
}
                        }
                    }
                    
else
                        
nullCCount++;
                
}
                
// Should have complete list of active contacts now so send them
                
if (_ProcOk)
                
{
                    sendTUIO_2DCur
(activeContacts);
                    
bundleCount++;
                
}
                
// Now act on if we have any contacts
                
activeContactCount activeContacts.Count;
                if (
activeContactCount == 0)
                    
_ProcOk false;

                if (
isApplicationActivated)
                
{
                    
// Only do the following if the application is activated.
                    // Stuff in here which deals with application such as control of Flash player.
                
}

                
// TODO: Add your update logic here
            
}

            base
.Update(gameTime);
        
}

Note that _ProcOk boolean is there to prevent the sending of Alive messages when there is no more active points other than a last “empty” alive message. The value _Inact was there to increase to only process on fewer iterations of the Update() process - which by default is every 20ms.

Note that the check for not sending anything if the contact count is zero has been removed from sendTUIO_2DCur() above.

bamalam

Profile
 
 
Posted: 18 March 2010 05:12 PM   [ Ignore ]   [ # 5 ]
Avatar
Rank
Joined  2009-09-15
Total Posts:  78
New Member
bamalam - 18 March 2010 04:16 PM

However although now I’m seeing touch points added and removed there is a problem when I hold a touch point and move it around. Although it keeps the ID of the message the touch is blinking on and off as if it is losing some of the alive messages.

In fact I’ve discovered that it sometimes does not pick up on new touch point as well.

Sounds like you’re done, actually.  :D This is the correct behavior—Surface does not necessarily update blobs at the same frame rate as your application (this would be very unlikely)—especially with OSC going between Surface and your app.  So you are drawing frames faster than you are receiving blob data, hence the flickering.  Also, “alive” messages really don’t work as advertised.  Instead, use only “set” messages and a “timeout” scheme to keep blobs on screen.

Now that you are maintaining a list of active Surface contacts and receiving updates over UDP, the next step is to create a blob manager.  This needs to happen inside your client program.  That is, if you are taking Surface data and sending it out to Flash, you need to write a blob manager in Flash.

Here’s a quick overview on how to do that.  I’m sticking with C#/XNA for the example, but this should all translate easily to AS3 (I’ve done this in AS3 quite a bit, as well, but that code’s a tad outdated).  You’ll need a list of blobs, a Blob class/structure, and a something to keep track of elapsed time.  You might want to use a variable to say how long you want a blob to “live” before it’s removed from the screen.

public class myApp Game
{

     int elapsedTime
;
     
int aliveTime 75;
     List<
BlobBlobList;

     ...

}

public struct Blob
{

     int ID
;
     
int LastUpdateTime;
     
int x;
     
int y;

}

When you receive a TUIO “set” message, you’ll want to use that data to determine whether you should add a blob or update one.  In your XNA game class, you’ll want a packet reading function like this:

/// <summary>
/// Reads OSC packet
/// </summary>
/// <param name="packet">The packet to read</param>
protected void readPacket(OSCPacket packet)
{

    
// Check packet
    
if (packet != null)
    
{

        
// Iterate through the messages
        
foreach (OSCMessage message in packet.Values)
        
{

            
// Set message
            
if (message.Values[0].ToString() == "set")
            
{

                
// Get the blob's ID and position
                // (DISPLAY_W and DISPLAY_H are screen size values I have in my settings)
                
int blobID Int32.Parse(message.Values[1].ToString());
                
int blobX Decimal.ToInt32(Decimal.Parse(message.Values[2].ToString()) * Properties.Settings.Default.DISPLAY_W);
                
int blobY Decimal.ToInt32(Decimal.Parse(message.Values[3].ToString()) * Properties.Settings.Default.DISPLAY_H);

                
// Set a found flag
                
bool found false;

                
// Iterate through all blobs in list
                
int blobCount BlobList.Count;
                for (
int i 0blobCounti++)
                
{
                    
                    
// Blob found
                    
if (BlobList[i].ID == ID)
                    
{
                        
                        
// Set found flag
                        
found true;
                        
                        
// Update blob in list
                        
BlobList[i].blobX;
                        
BlobList[i].blobY;
                        
BlobList[i].LastUpdate elapsedTime;
                        
                    
}
                    
                }
                
                
// Blob not found
                
if (!found)
                
{
                    
                    
// Create a new blob and set its properties
                    
Blob blob = new Blob();
                    
blob.ID blobID;
                    
blob.blobX;
                    
blob.blobY;
                    
blob.LastUpdate elapsedTime;
                    
                    
// Add the blob to the list
                    
BlobList.add(blob);
                    
                
}

            }
            
        }

    }
    
}

Note that I’m setting the Blob.LastUpdate using the “elapseTime” property I mentioned at the top of this post.  Inside of your update function, use GameTime.ElapsedRealTime to updated the class’ “elapsedTime” property, like so…

protected override void Update(GameTime gt)
{

     
// Update elapsed time
     
elapsedTime gt.ElapsedRealTime;

     ...

}

The last thing to do is to “timeout” a blob.  Use “elapsedTime”, “aliveTime”, and Blob.LastUpdate to determine when a blob has gone too long without an update and then remove it.  In my experience, 75 - 100ms is a good “alive” time when you take the UDP communication and packet parsing into consideration.

protected override void Update(GameTime gt)
{

     
// Update elapsed time
     
elapsedTime gt.ElapsedRealTime;

    
// Iterate through all blobs in list
    
for (int i 0BlobList.Counti++)
    
{
        
        
// Remove blob from list if it hasn't 
        // been updated in "aliveTime"
        
if (BlobList[i].LastUpdate elaspedTime aliveTimeBlobList.RemoveAt(i);
        
    
}

    
...

}

Now iterate through BlobList in your draw routine to draw active blobs.  Voila!

Profile
 
 
   
 
 
‹‹ Small Query      Com data to tuio ››