Category Archives: Tech Tips

How to get rock solid multi-touch input finger tracking on iOS

So I’ve got this game prototype idea I wanted to try but I needed better multi-touch input support (accurate and comprehensive fingerID tracking) to make it happen.

The new test in RTSimpleApp: You can't see it in the shot, but each square has a fingerID # on it as well

It works great and has been added to the Proton SDK svn along with a new “Multitouch input test” option in RTSimpleApp.

Some stuff I noticed:

  • Tracks 11 fingers on an iPad
  • Tracks 4 fingers on an iPod Touch G1 (5 sort of.. but not reliably..)
  • Tracks 5 fingers on an iPhone4
  • Impossible to confuse it with fast movement, sliding off the screen or fast button mashing.  Solid!
  • Tracks 1.5 fingers on a Google Nexus One (It’s the HW’s fault.  Curious to see the results from a Tab or other new devices though)

For those interested, here is the relevant tracking code to get the “finger id”:  (yeah, I could have done it a more Obj-C way, but meh.. also, you may need your file named .mm, not .m for this code to work…)

const int MAX_TOUCHES= 11; //Oops, it can handle 11, not 10.  Thanks @Bob_at_BH

class TouchTrack
{
public:

	TouchTrack()
	{
		m_touchPointer = NULL;
	}

	void *m_touchPointer;
};

TouchTrack g_touchTracker[MAX_TOUCHES];

int GetFingerTrackIDByTouch(void* touch)
{
		for (int i=0; i < MAX_TOUCHES; i++)
		{
			if (g_touchTracker[i].m_touchPointer == touch)
			{
				return i;
			}
		}

	//LogMsg("Can't locate fingerID by touch %d", touch);
	return -1;
}

int AddNewTouch(void* touch)
{
		for (int i=0; i < MAX_TOUCHES; i++)
		{
			if (!g_touchTracker[i].m_touchPointer)
			{
				//hey, an empty slot, yay
				g_touchTracker[i].m_touchPointer = touch;
				return i;
			}
		}

	LogMsg("Can't add new fingerID");
	return -1;
}

int GetTouchesActive()
{
int count = 0;

	for (int i=0; i < MAX_TOUCHES; i++)
		{
			if (g_touchTracker[i].m_touchPointer)
			{
				count++;
			}
		}
return count;
}

// Handles the start of a touch
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
  	// Enumerate through all the touch objects.

	for (UITouch *touch in touches)
	{
		//found a touch.  Is it already on our list?
		int fingerID = GetFingerTrackIDByTouch(touch);

		if (fingerID == -1)
		{
			//add it to our list
			fingerID = AddNewTouch(touch);
		} else
		{
			//already on the list.  Don't send this
			//LogMsg("Ignoring touch %d", fingerID);
			continue;
		}

		CGPoint pt =[touch locationInView:self];
		ConvertCoordinatesIfRequired(pt.x, pt.y);
		GetMessageManager()->SendGUIEx(MESSAGE_TYPE_GUI_CLICK_START,pt.x, pt.y,fingerID);			
	}	

	  
	#ifdef _DEBUG
	//LogMsg("%d touches active", GetTouchesActive());
	#endif
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
  	// Enumerate through all the touch objects.
	for (UITouch *touch in touches)
	{
		//found a touch.  Is it already on our list?
		int fingerID = GetFingerTrackIDByTouch(touch);
		if (fingerID != -1)
		{
			g_touchTracker[fingerID].m_touchPointer = NULL; //clear it
		} else
		{
			//wasn't on our list
			continue;
		}
		
		CGPoint pt =[touch locationInView:self];
		ConvertCoordinatesIfRequired(pt.x, pt.y);
		GetMessageManager()->SendGUIEx(MESSAGE_TYPE_GUI_CLICK_END,pt.x, pt.y, fingerID);
	}	
}

- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
  	// Enumerate through all the touch objects.
	for (UITouch *touch in touches)
	{
		//found a touch.  Is it already on our list?
		int fingerID = GetFingerTrackIDByTouch(touch);
		if (fingerID != -1)
		{
			g_touchTracker[fingerID].m_touchPointer = NULL; //clear it
		} else
		{
			//wasn't on our list
			continue;
		}
		
		CGPoint pt =[touch locationInView:self];
		ConvertCoordinatesIfRequired(pt.x, pt.y);
		GetMessageManager()->SendGUIEx(MESSAGE_TYPE_GUI_CLICK_END,pt.x, pt.y, fingerID);
	}	
}


- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
   // Enumerate through all the touch objects.
	for (UITouch *touch in touches)
	{
	
		//found a touch.  Is it already on our list?
		int fingerID = GetFingerTrackIDByTouch(touch);
		if (fingerID != -1)
		{
			//found it
		} else
		{
			//wasn't on our list?!
			continue;
		}
	
		CGPoint pt =[touch locationInView:self];
		ConvertCoordinatesIfRequired(pt.x, pt.y);
		GetMessageManager()->SendGUIEx(MESSAGE_TYPE_GUI_CLICK_MOVE,pt.x, pt.y, fingerID);
	}	
}