In this recipe, we will learn how to create objects responsible to multi-touch gestures, such as dragging, scaling, or rotating by extending the InteractiveObject
class mentioned in the Creating an interactive object that responds to the mouse recipe of this chapter. We are going to build an iOS application that uses iOS device multi-touch capabilities.
Please refer to the Creating an interactive object that responds to the mouse recipe to find the InteractiveObject
class headers and source code and Creating a project for an iOS touch application recipe from Chapter 1.
We will create an iPhone application with sample objects that can be dragged, scaled, or rotated.
TouchInteractiveObject.h
to your project:#pragma once #include "cinder/app/AppNative.h" #include "cinder/gl/gl.h" #include "cinder/Color.h" #include "InteractiveObject.h" using namespace std; using namespace ci; using namespace ci::app; class TouchInteractiveObject : public InteractiveObject { public: TouchInteractiveObject( const Vec2f& position, const Vec2f& size ); bool touchesBegan(TouchEvent event); bool touchesMoved(TouchEvent event); bool touchesEnded(TouchEvent event); Vec2f getPosition() { return position; } void setPosition(Vec2f position) { this->position = position; } void setPosition(float x, float y) { setPosition(Vec2f(x,y)); } float getWidth() { return getSize().x; } float getHeight() { return getSize().y; } Vec2f getSize() { return rect.getSize(); } void setSize(Vec2f size) { size.x = max(30.f,size.x); size.y = max(30.f,size.y); rect = Rectf(getPosition()-size*0.5f,getPosition()+size*0.5f); } void setSize(float width, float height) { setSize(Vec2f(width,height)); } float getRotation() { return rotation; } void setRotation( float rotation ) { this->rotation = rotation; } virtual void draw(); protected: Vec2f position; float rotation; bool scaling; unsigned int dragTouchId; unsigned int scaleTouchId; };
TouchInteractiveObject.cpp
to your project and include the previously created header file by adding the following code line:#include "TouchInteractiveObject.h"
TouchInteractiveObject
:TouchInteractiveObject::TouchInteractiveObject( const Vec2f& position, const Vec2f& size ) : InteractiveObject( Rectf() ) { scaling = false; rotation = 0.f; setPosition(position); setSize(size); AppNative::get()->registerTouchesBegan(this, &TouchInteractiveObject::touchesBegan); AppNative::get()->registerTouchesMoved(this, &TouchInteractiveObject::touchesMoved); AppNative::get()->registerTouchesEnded(this, &TouchInteractiveObject::touchesEnded); }
bool TouchInteractiveObject::touchesBegan(TouchEvent event)
{ Vec2f bVec1 = getSize()*0.5f; Vec2f bVec2 = Vec2f(getWidth()*0.5f, -getHeight()*0.5f); bVec1.rotate((rotation) * (M_PI/180.f)); bVec2.rotate((rotation) * (M_PI/180.f)); Vec2f bVec; bVec.x = math<float>::max( abs(bVec1.x), abs(bVec2.x)); bVec.y = math<float>::max( abs(bVec1.y), abs(bVec2.y)); Area activeArea = Area(position-bVec, position+bVec); for (vector<TouchEvent::Touch>::const_iterator it = event.getTouches().begin(); it != event.getTouches().end(); ++it) { if(activeArea.contains( it->getPos() )) { if(mPressed) { scaling = true; scaleTouchId = it->getId(); } else { mPressed = true; dragTouchId = it->getId(); } } } return false; } bool TouchInteractiveObject::touchesMoved(TouchEvent event) { if(!mPressed) return false; const TouchEvent::Touch* dragTouch; const TouchEvent::Touch* scaleTouch; for (vector<TouchEvent::Touch>::const_iterator it = event.getTouches().begin(); it != event.getTouches().end(); ++it) { if (scaling && scaleTouchId == it->getId()) { scaleTouch = &(*it); } if(dragTouchId == it->getId()) { dragTouch = &(*it); } } if(scaling) { Vec2f prevPos = (dragTouch->getPrevPos() + scaleTouch->getPrevPos()) * 0.5f; Vec2f curPos = (dragTouch->getPos() + scaleTouch->getPos())*0.5f; setPosition(getPosition() + curPos - prevPos); Vec2f prevVec = dragTouch->getPrevPos() - scaleTouch->getPrevPos(); Vec2f curVec = dragTouch->getPos() - scaleTouch->getPos(); float scaleFactor = (curVec.length() - prevVec.length()) / prevVec.length(); float sizeFactor = prevVec.length() / getSize().length(); setSize(getSize() + getSize() * sizeFactor * scaleFactor); float angleDif = atan2(curVec.x, curVec.y) - atan2(prevVec.x, prevVec.y); rotation += -angleDif * (180.f/M_PI); } else { setPosition(getPosition() + dragTouch->getPos() - dragTouch->getPrevPos() ); } return false; } bool TouchInteractiveObject::touchesEnded(TouchEvent event) { if(!mPressed) return false; for (vector<TouchEvent::Touch>::const_iterator it = event.getTouches().begin(); it != event.getTouches().end(); ++it) { if(dragTouchId == it->getId()) { mPressed = false; scaling = false; } if(scaleTouchId == it->getId()) { scaling = false; } } return false; }
draw
method for TouchInteractiveObjects
:void TouchInteractiveObject::draw() { Rectf locRect = Rectf(Vec2f::zero(), getSize()); gl::pushMatrices(); gl::translate(getPosition()); gl::rotate(getRotation()); gl::pushMatrices(); gl::translate(-getSize()*0.5f); gl::color(Color::gray( mPressed ? 0.6f : 0.9f )); gl::drawSolidRect(locRect); gl::color(Color::black()); gl::drawStrokedRect(locRect); gl::popMatrices(); gl::popMatrices(); }
TouchInteractiveObject
, but overrides the draw
method and, in this case, we want our interactive object to be a circle. Add the following class definition to your main source file:class Circle : publicTouchInteractiveObject { public: Circle(const Vec2f& position, const Vec2f& size) : TouchInteractiv eObject(position, size) {} virtual void draw() { gl::color(Color::gray( mPressed ? 0.6f : 0.9f )); gl::drawSolidEllipse(getPosition(), getSize().x*0.5f, getSize().y*0.5f); gl::color(Color::black()); gl::drawStrokedEllipse(getPosition(), getSize().x*0.5f, getSize().y*0.5f); } };
#include "cinder/app/AppNative.h" #include "cinder/Camera.h" #include "cinder/Rand.h" #include "TouchInteractiveObject.h"
typedef
declaration:typedef shared_ptr<TouchInteractiveObject> tio_ptr;
tio_ptr mObj1; tio_ptr mCircle;
setup
method initialize the objects:mObj1 = tio_ptr( new TouchInteractiveObject(getRandPos(), Vec2f(100.f,100.f)) ); mCircle = tio_ptr( new Circle(getRandPos(), Vec2f(100.f,100.f)) );
draw
method is simple and looks as follows:gl::setMatricesWindow(getWindowSize()); gl::clear( Color::white() ); mObj1->draw(); mCircle->draw();
setup
method we are using the function getRandPos
, which returns a random position in screen boundaries with some margin:Vec2f MainApp::getRandPos() { return Vec2f( randFloat(30.f, getWindowWidth()-30.f), randFloat(30.f, getWindowHeight()-30.f)); }
We created the
TouchInteractiveObject
class by inheriting and overriding the InteractiveObject
methods and properties. We also extended it with methods for controlling position and dimensions.
In step 3, we are initializing properties and registering callbacks for touch events. The next step is to implement these callbacks. On the
touchesBegan
event, we are checking if the object is touched by any of the new touches, but all the calculations of movements and gestures happen during touchesMoved
event.
In step 6, you can see how simple it is to change the appearance and keep all the interactive capabilities of TouchInteractiveObject
by overriding the draw
method.
You can notice an issue that you are dragging multiple objects while they are overlapping. To solve that problem, we will add a simple object activation manager.
class ObjectsManager { public: ObjectsManager() { } void addObject( tio_ptr obj) { objects.push_front(obj); } void update() { bool rel = false; deque<tio_ptr>::const_iterator it; for(it = objects.begin(); it != objects.end(); ++it) { if( rel ) (*it)->release(); else if( (*it)->isActive() ) rel = true; } } protected: deque<tio_ptr> objects; };
shared_ptr<ObjectsManager> mObjMgr;
setup
method initialize mObjMgr
, which is the object's manager, and add the previously initialized interactive objects:mObjMgr = shared_ptr<ObjectsManager>( new ObjectsManager() ); mObjMgr->addObject( mObj1 ); mObjMgr->addObject( mCircle );
update
method to your main class as follows:void MainApp::update() { mObjMgr->update(); }
TouchInteractiveObject
class:bool isActive() { return mPressed; } void release() { mPressed = false; }
3.139.67.5