First contact: SDL 2 touch gestures

SDL touch gestures

There are two different types of gestures supported in SDL2 - I've just added some code to support both into my small game CaveExpress. The code is available on github.

$1 gesture recognition system

With SDL you can record, load and detect $1 gestures.

Recording

First of all you have to record your gesture. I'm doing this in my UI by pushing a particular window onto the stack that is listening to finger events and is starting the recording in some onPush callback. By listening to the finger event, I can just draw the coordinates for the gesture into the window. Once you lift the fingers the gesture is saved and window is popped from the UI stack.

void UIGestureWindow::render (int x, int y) const {
    UIWindow::render(x, y);
    for (CoordsConstIter i = _coords.begin(); i != _coords.end(); ++i) {
        const vec2& v = *i;
        _frontend->renderRect(x + v.x, y + v.y, 4, 4, colorRed);
    }
}

bool UIGestureWindow::onFingerMotion (int64_t finger, uint16_t x, uint16_t y, int16_t dx, int16_t dy) {
    const bool retVal = UIWindow::onFingerMotion(finger, x, y, dx, dy);
    _coords.push_back(vec2(x, y));
    return retVal;
}

void UIGestureWindow::onActive() {
    UIWindow::onActive();

    _coords.clear();

    if (!SDL_RecordGesture(-1)) {
        info(LOG_CLIENT, "Could not start gesture recording");
    } else {
        info(LOG_CLIENT, "Started gesture recording");
    }
}

bool UIGestureWindow::onGestureRecord (int64_t gestureId)
{
    info(LOG_CLIENT, "Save gestures");
    const bool retVal = UIWindow::onGestureRecord(gestureId);
    const URI uriLocal("file://" + FS.getAbsoluteWritePath() + "gesture-" + string::toString(gestureId));
    std::string path;
    const FilePtr& f = FS.getFile(uriLocal);
    SDL_RWops* rwops = SDL_RWFromFile(f->getURI().getPath().c_str(), "wb");
    if (rwops) {
        info(LOG_CLIENT, "Save gestures to " + f->getURI().getPath());
        if (SDL_SaveDollarTemplate(gestureId, rwops) <= 0) {
            info(LOG_CLIENT, "Failed to save gestures to " + f->getURI().getPath());
        }
        SDL_RWclose(rwops);
    } else {
        info(LOG_CLIENT, "Failed to save gestures to " + f->getURI().getPath());
    }
    UI::get().pop();
    return retVal;
}

onGestureRecord() is called after SDL send the SDL_DOLLARRECORD event from SDL_RecordGesture call.

Load 

I'm loading the saved gesture from memory by converting the saved gesture into a header and just do this:

bool UI::loadGesture (const unsigned char* data, int length)
{
    SDL_RWops* rwops = SDL_RWFromConstMem(static_cast<const void*>(data), length);
    const int n = SDL_LoadDollarTemplates(-1, rwops);
    SDL_RWclose(rwops);
    if (n == -1) {
        const std::string e = SDL_GetError();
        error(LOG_CLIENT, "Failed to load gesture: " + e);
        return false;
    } else if (n == 0) {
        info(LOG_CLIENT, "Could not load gesture");
        return false;
    }

    info(LOG_CLIENT, "Loaded gesture");
    return true;
} 

Handle

SDL will send an SDL_DOLLARGESTURE with the gesture id (which is guaranteed to stay stable) an error (how close the detection was to your saved gesture) and some other values. You can bind the gesture id to some of your actions and so on - but make sure to check the error. It can also be useful to add some meta data to your gesture ids. Maybe provide some mapping which e.g. defines the threshold of the error for a gesture and/or e.g. the number of fingers that were used.

Multigestures

Standard gestures like swiping, rotating and pinching are supported by multi gestures. The event you have to handle is SDL_MULTIGESTURE. Depending on the event delivered values for the relative rotation and the relative distance you can determine which of gesture it is.

bool IUIMapWindow::onMultiGesture (float theta, float dist, int32_t numFingers)
{
    const bool retVal = UIWindow::onMultiGesture(theta, dist, numFingers);
    if (numFingers == 2) {
        const float currentZoom = _nodeMap->getMap().getZoom();
        const float step = dist;
        if (dist > 0.0f) {
            _nodeMap->getMap().setZoom(currentZoom - step);
        } else if (dist < 0.0f) {
            _nodeMap->getMap().setZoom(currentZoom + step);
        }
    }
    return retVal;
}

Kommentare

Beliebte Posts