Dragging, scaling, and rotating objects using multi-touch

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.

Dragging, scaling, and rotating objects using multi-touch

Getting ready

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.

How to do it…

We will create an iPhone application with sample objects that can be dragged, scaled, or rotated.

  1. Add a new header file named 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;
    };
  2. Add a new source file named TouchInteractiveObject.cpp to your project and include the previously created header file by adding the following code line:
    #include "TouchInteractiveObject.h"
  3. Implement the constructor of 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);
    }
  4. Implement the handlers for touch events:
    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;
    }
  5. Now, implement the basic 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();
    }
  6. Here is the class, which inherits all the features of 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);
     } 
    };
  7. Now take a look at your main application class file. Include the necessary header files:
    #include "cinder/app/AppNative.h"
    #include "cinder/Camera.h"
    #include "cinder/Rand.h"
    
    #include "TouchInteractiveObject.h"
  8. Add the typedef declaration:
    typedef shared_ptr<TouchInteractiveObject> tio_ptr;
  9. Add members to your application class to handle the objects:
    tio_ptr mObj1;
    tio_ptr mCircle;
  10. Inside the 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)) );
  11. The draw method is simple and looks as follows:
    gl::setMatricesWindow(getWindowSize());
    gl::clear( Color::white() );
    mObj1->draw();
    mCircle->draw();
  12. As you can see in the 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));
    }

How it works…

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.

There is more…

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.

  1. Add a new class definition to your Cinder application:
    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;
    };
  2. Add a new member to your application's main class:
    shared_ptr<ObjectsManager> mObjMgr;
  3. At the end of the 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 );
  4. Add the update method to your main class as follows:
    void MainApp::update()
    {
        mObjMgr->update();
    }
  5. Add two new methods to the TouchInteractiveObject class:
    bool    isActive() { return mPressed; }
    void    release() { mPressed = false; }
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
13.59.197.213