User Tools

Site Tools


tutorials:cpp_open_frameworks:getting_started_2_hello_multitouch

Getting Started II (Hello Multitouch)

Introduction

In this tutorial, you will be creating a simple application to display touchpoint data on the screen, using the C++ bindings to connect to the GestureWorks Core DLL. You will learn how to load and initialize GestureWorks, as well as how to receive basic touchpoint data. For this tutorial you will need the GestureWorks Core multitouch framework; a free trial is available.

Download the code for all of the C++ & openFrameworks multitouch tutorials here: tutorials_cpp_openframeworks.zip


Requirements

Estimated time to complete: 1 hour

  • Working knowledge of C++
  • GestureWorks Core license and SDK files
  • Microsoft Windows 7, 8 or 10
  • Microsoft Visual Studio 2012 (Express or Professional)
  • A copy of openFrameworks (see the openFrameworks website for documentation and downloads)
  • Multitouch display device
  • Visual Studio solution configured to work with GestureWorks Core (see C++ & OpenFrameWorks: Getting Started I (Hello World)) and openFrameworks

Process Overview

    • Configuration
    • Connecting to the Gestureworks.dll
    • Registering the Display Window
    • Displaying Touch Points
    • Handling Frame Rate
    • Processing Frames
    • Consuming Touchpoint Data

Process Detail

1. Why openFrameworks?

OpenFrameworks is one of the most easy-to-use frameworks for getting started with interactive multimedia applications. Additionally, it has broad community support, and comes with example programs that concisely demonstrate each of its features. For the purposes of our tutorials, openFrameworks provides a quick, clean way to write a graphical application for integration with GestureWorks.

2. Setup

If you have gone through the first tutorial, you should be familiar with the process for configuring a Visual Studio solution to make use of GestureWorks Core. In addition, you will need to set up your solution to use OpenFrameworks.

In short, the .cpp and .h files for GestureWorks Core and GWCUtils that were included in your installation package need to be added to the source tree for your project, and the directory containing these files needs to be added to VC++ Directories in your project settings.

For this tutorial, you will also need to install OpenFrameworks, and add it to your project. There are no official tutorials for setting up OpenFrameworks with Visual Studio.

Whichever instructions you choose to follow, add OpenFrameworks to your project with the GestureWorks Core bindings. If you are using any version of Visual Studio past 2012, it is important to note that you will also need to have the 2012 toolset installed. You will also need to visit Configuration Properties → General → Platform Toolset, and select Visual Studio 2012 (v110) for any projects that include OpenFrameworks, as it does not yet have a release compatible with any future version of Visual Studio.

Once the solution is configured properly, the first step in the application is to write the application class and main function. The application class is contained in the files HelloMultitouch.cpp and HelloMultitouch.h, and main is, appropriately, contained in main.cpp.

The main function is quite short:

main.cpp

#include "ofMain.h"
#include "HelloMultitouch.h"

//========================================================================
int main( ){

    ofSetupOpenGL(1024,768, OF_WINDOW);

    // Set the window title so we can register it with GestureWorks later
    ofSetWindowTitle(HelloMultitouch::window_title);

    // Start the app
    ofRunApp( new HelloMultitouch());

}

Effectively, all we’re doing is initializing the window, and telling OpenFrameworks to run our application class. Because we’ll be attaching GestureWorks by window name later, we need to see the name of the window.

Moving on to Hello Multitouch.h, OpenFrameworks applications all derive from the base class ofBaseApp, so our app needs to inherit from this class:

HelloMultitouch.h

#pragma once

#include <unordered_map>
#include "ofMain.h"
#include "Drawables.h"
#include "GestureWorks.h"

class HelloMultitouch : public ofBaseApp{
public:
    HelloMultitouch();

    // openFrameworks overrides
    void setup();
    void update();
    void draw();
		
    const static std::string window_title;
private:
    clock_t last_tick;
    std::unordered_map<long, GWCTutorials::TouchPoint> touch_points;

    void handlePointEvents(const std::vector<gwc::PointEvent>& point_events);
};

setup is called once during initialization. update and draw are called one after another until the program exits, and will contain the bulk of our logic.

HelloMultitouch.cpp, the setup function calls functions required to load and initialize GestureWorks:

HelloMultitouch.cpp

// openFrameworks calls this function once before the app starts
void HelloMultitouch::setup(){
    if (!GestureWorks::Instance()->LoadGestureWorks(L"GestureWorksCore32.dll")){
        MessageBox(NULL, L"Unable to load GestureWorksCore DLL", L"Error", 0);
        return;
    }

    // Initialize with (0,0) so GestureWorks will automatically detect our screen resolution
    GestureWorks::Instance()->InitializeGestureWorks(0, 0);

    // Register our window with GestureWorks for touch input. We could also do this with an HWND.
    // Alternatively, we could use some other method of getting touch input and feed it to GestureWorks via
    // the addEvent method. 
    if (!GestureWorks::Instance()->RegisterWindowForTouchByName(std::wstring(window_title.begin(), window_title.end()))){
        MessageBox(NULL, L"Unable to register window for touch", L"Error", 0);
        return;
    }
}

LoadGestureworks can be given a relative or absolute path to the GestureWorks DLL. For this example, we are assuming the DLL is in the same directory as the application. This will load the DLL and import all of the functions required to interact with it through the bindings. It is critical that this is the first function called from the bindings. If any other functions are called before this one, your program will likely suffer an access violation exception. If the call succeeds, it will return 0. If the call fails, it will return an error code to indicate why it failed.

It is critical, at this point, that you have loaded the DLL that corresponds to your architecture. GestureWorks comes with both 32- and 64-bit DLLs, and your compiled project must load the DLL whose architecture matches your target architecture. If you attempt to load a 64-bit DLL into a 32-bit program, or vice versa, the function will fail. If you are unsure which is your target architecture, you can check under Project Properties → Platform.

LoadGML (Not called here since it's only needed for Gesture definitions) can also be given a relative or absolute path to the GML file you wish to use with the program. This will cause GestureWorks to load and parse a GML file of your choosing. The call will return true if it succeeds, and false if it cannot find the GML file specified.

InitializeGestureWorks function initializes the core. If 0 is passed for both parameters, GestureWorks Core will initialize based on the screen width and height. This is good for most cases.

RegisterWindowForTouchByName takes the name of the window with which we wish to use GestureWorks, and returns true if it successfully connects to it. For precision, we could also call registerWindowForTouch – which accepts a handle to the window – but acquiring the window handle is outside the scope of this tutorial. This is the last function that needs to be called before processing events.

3. The Draw Function

Included in the tutorial zip file is Drawables.cpp and Drawables.h, this includes all the code for drawing the circles for this tutorial.

All of our actual visualization is triggered from within the draw function.

HelloMultitouch.cpp

// openFrameworks calls this function in an infinite loop until the app ends 
void HelloMultitouch::draw(){
	for (const auto& point : touch_points){
		point.second.Draw();
	}
}

Call Draw for each TouchPoint in the touch_points map. Using the x and y coordinates of each TouchPoint object, as well as the point’s ID to present each active TouchPoint as an outlined circle with it’s x and y coordinates and it’s ID adjacent:

Drawables.cpp

	void TouchPoint::Draw() const{
		ofSetHexColor(color);
		ofFill();
		ofCircle(x, y, radius - 10);
		ofNoFill();
		ofSetLineWidth(2);
		ofCircle(x, y, radius);

		std::stringstream info;
		info << "ID: " << id << " X: " << x << " Y: " << y;
		ofDrawBitmapString(info.str(), x + 40, y - 40);
	}

We call ofFill to indicate that the next draw command should draw a solid object before calling ofCircle to draw the center circle with radius of 20 pixels. Then, we call ofNoFill to indicate that the next object drawn should be only an outline, and call ofCircle again to draw a ring around the circle we just drew, with a radius of 30 pixels.

Last, we pass the x and y coordinate values as well as the point’s ID into a set of stringstreams so that we can assemble a string with these values for ofDrawBitmapString, which will draw the given text at the given coordinates. We place this text a little over 40 pixels away from the touchpoints so that the values can be read next to the user’s fingertips.

If you are having difficulty with the display functions, it may be useful to print this info to the console using std::cout. This can help you to determine whether you’re receiving coordinates correctly.

4. The update Function

The update function is called once per cycle, prior to the draw function. It is here that we will manage program time, cue GestureWorks to process input, and consume any events it generates.

Handling Frame Rate

The first order of business is to see how much time has passed since the last time an update was run by calling GetTickCount. This function returns the time, in milliseconds, that the program has been running. In this example, we want to process frames at a rate of about 60 frames per second, which corresponds to a frame delay between 16 and 17 milliseconds. This being the case, we check to see if 16 milliseconds have passed since the last time we processed and, if not, return without processing:

HelloMultitouch.cpp

// openFrameworks calls this function before every draw cycle
void HelloMultitouch::update(){
	// For consistency, we try to run GestureWorks at a constant rate - about 60Hz in this case
	clock_t this_tick(clock());
	if (this_tick - last_tick < 16) return;
	last_tick = this_tick;

	// Tell GestureWorks to process all of the data since the last frame
	GestureWorks::Instance()->ProcessFrame();

	// Get all the point events from the current frame
	std::vector<gwc::PointEvent> point_events = GestureWorks::Instance()->ConsumePointEvents();
	handlePointEvents(point_events);
}

Processing Frames

If we're continuing on, we call processFrame. This function tells GestureWorks to take all touch input received since the last time we called the function, process it, and queue up any events generated with the input. This process keeps the GestureWorks core engine moving along and is necessary for meaningful data to be returned in the form of PointEvents.

Consuming touchpoint data

Any touchpoint events generated from our call to processFrame can then be acquired by calling consumePointEvents.

This will return a vector of gwc::PointEvent objects. Each of these objects contains a gwc::Point object describing its location and dimensions, as well as an ID number, a timestamp for the event, and a gwc::touchStatus enum indicating the nature of the event. The vector returned is a chronologically-ordered list of each of these events that occurred between the last two calls to processFrame. Together, this data allows us to identify and keep track of all points added to, moved around on, and removed from the application window. To handle the data we just consumed, we call handlePointEvent that adds all the point events that are returned from GestureWorksCore into the touch_points map for drawing.

HelloMultitouch.cpp

void HelloMultitouch::handlePointEvents(const std::vector<gwc::PointEvent>& point_events){
	for (const auto& point : point_events){
		if (point.status == gwc::TOUCHADDED || point.status == gwc::TOUCHUPDATE){

			// By default, GestureWorks sends us coordinates in normalized screen space. To get to localized pixel
			// coordinates we need to do a conversion.
			std::pair<int, int> screen_coords(normScreenToWindowPx(point.position.x, point.position.y));

			// Update the touchpoints position
			touch_points[point.point_id] = TouchPoint(point.point_id, screen_coords.first, screen_coords.second);
		}
		else{	
			touch_points.erase(point.point_id);
		}
	}
}

Because we're using a map of ID's to point data, we can use the same logic for point updates and point additions, since both addition and value assignments use the same logic. If we've received a gwc::TOUCHREMOVED event, we simply erase the point from the map.


Troubleshooting

If any of the initialization functions fail, ensure that they’re being called with the correct path information. If initialization succeeds, but you aren’t receiving any touch data, double-check that you called registerWindowForTouchByName with the exact title of the window. If you are still having issues, confirm that you are loading the GestureWorksCore.dll file that corresponds to your operating system version (32-bit vs 64-bit).


Next tutorial: C++ & openFrameworks: Interactive Bitmaps
Previous tutorial: C++ & openFrameworks: Getting Started I (Hello World)

tutorials/cpp_open_frameworks/getting_started_2_hello_multitouch.txt · Last modified: 2015/11/12 15:39 by glass