Creating an image gallery in 3D

In this recipe we will learn how we can create an image gallery in 3D. The images will be loaded from a folder selected by the user and displayed in a three-dimensional circular fashion. Using the keyboard, the user will be able to change the selected image.

Getting ready

When starting the application you will be asked to select a folder with images, so make sure you have one.

Also, in your code include the necessary files to use OpenGL drawing calls, textures, the timeline, and loading images.

#include "cinder/gl/gl.h"
#include "cinder/gl/Texture.h"
#include "cinder/Timeline.h"
#include "cinder/ImageIo.h"

Also, add the following useful using statements:

using namespace ci;
using namespace ci::app;
using namespace std;

How to do it…

We will display and animate images in 3D space. Perform the following steps to do so:

  1. We will start by creating an Image class. Add the following code snippet before the main application class:
    class Image{
    public:
    Image( gl::Texture texture, constRectf& maxRect ){
     this->texture = texture;
     distance = 0.0f;
     angle = 0.0f;
     Vec2f size = Vec2f(texture.getWidth(), texture.getHeight());
     rect = Rectf(-size * 0.5f, size*0.5f).getCenteredFit( 
      maxRect, true );
    }
    void draw(){
     gl::pushMatrices();
     glRotatef( angle, 0.0f, 1.0f, 0.0f );
     gl::translate( 0.0f, 0.0f, distance );
     gl::draw( texture, rect );
     gl::popMatrices();
    }
    gl::Texture texture;
    float distance;
    float angle;
    Rectfrect;
    }
  2. In the main application's class we will declare the following members:
    vector<shared_ptr<Image>> mImages;
    int mSelectedImageIndex;
    Anim<float> mRotationOffset;
  3. In the setup method we will ask the user to select a folder and then try to create a texture from each file in the folder. If a texture is successfully created, we will use it to create an Image object and add it to mImages.
    fs::path imageFolder = getFolderPath( "" );
    if( imageFolder.empty() == false ){
     for( fs::directory_iterator it( imageFolder ); it !=
      fs::directory_iterator(); ++it ){
      const fs::path& file = it->path();
      gl::Texture texture;
      try {
       texture = loadImage( file );
      } catch ( ... ) { }
      if( texture ){
       Rectf maxRect(RectfmaxRect( Vec2f( -50.0f, -50.0f),
        Vec2f( 50.0f,50.0f ) );
       mImages.push_back( shared_ptr<Image>( 
        new Image( texture, maxRect) ) );
      } 
     }
    }
  4. We need to iterate over mImages and define the angle and distance that each image will have from the center.
    float angle = 0.0f;
    float angleAdd = 360.0f / mImages.size();
    float radius = 300.0f;
    for( int i=0; i<mImages.size(); i++ ){
     mImages[i]->angle = angle;
     mImages[i]->distance = radius;
     angle += angleAdd;
    }
  5. Now we can initialize the remaining members.
    mSelectedImageIndex = 0;
    mRotationOffset = 0.0f;
  6. In the draw method, we will start by clearing the window, setting the window's matrices to support 3D, and enabling reading and writing in the depth buffer:
    gl::clear( Color( 0, 0, 0 ) ); 
    gl::setMatricesWindowPersp( getWindowWidth(), getWindowHeight() );
    gl::enableDepthRead();
    gl::enableDepthWrite();
  7. Next we will draw the images. Since all our images have been displayed around the origin, we must translate them to the center of the window. We will also rotate them around the y axis using the value in mRotationOffset. Everything will go in an if statement that will check if mImages contains any image, in case no image was generated during the setup.
  8. Add the following code snippet inside the draw method:
    if( mImages.size() ){
     Vec2f center = (Vec2f)getWindowCenter();
     gl::pushMatrices();
     gl::translate( center.x, center.y, 0.0f );
     glRotatef( mRotationOffset, 0.0f, 1.0f, 0.0f );
     for(vector<shared_ptr<Image> >::iterator it=mImages.begin();
      it != mImages.end(); ++it ){
      (*it)->draw();
     }
     gl::popMatrices();
    }
  9. Since the user will be able to switch images using the keyboard, we must declare the keyUp event handler.
    void keyUp( KeyEvent event );
  10. In the implementation of keyUp we will move the images on to the left or right-hand side depending on whether the left or right key was released.

    If the selected image was changed, we animate mRotationOffset to the correspondent value, so that the correct image is now facing the user.

    Add the following code snippet inside the keyUp method:

    bool imageChanged = false;
    if( event.getCode() == KeyEvent::KEY_LEFT ){
     mSelectedImageIndex--;
     if( mSelectedImageIndex< 0 ){
      mSelectedImageIndex = mImages.size()-1;
      mRotationOffset.value() += 360.0f;
     }
     imageChanged = true;
    } else if( event.getCode() == KeyEvent::KEY_RIGHT ){
     mSelectedImageIndex++;
     if( mSelectedImageIndex>mImages.size()-1 ){
      mSelectedImageIndex = 0;
      mRotationOffset.value() -= 360.0f;
     }
     imageChanged = true;
    }
    if( imageChanged ){
     timeline().apply( &mRotationOffset, 
      mImages[ mSelectedImageIndex]->angle, 1.0f, 
      EaseOutElastic() );
    }
  11. Build and run the application. You will be prompted to select a folder containing images that will then be displayed in a circular fashion. Press the left or right key on the keyboard to change the selected image.
    How to do it…

How it works…

The draw method of the Image class rotates the coordinate system around the y axis and then translates the image drawing on the z axis. This will extrude the image from the center facing outwards on the given angle. It is an easy and convenient way of achieving the desired effect without dealing with coordinate transformations.

The Image::rect member is used to draw the texture and is calculated to fit inside the rectangle passed in the constructor.

When selecting the image to be displayed in front, the value of mRotationOffset will be the opposite of the image's angle, making it the image being drawn in front of the view.

In the keyUp event we check whether the left or right key was pressed and animate mRotationOffset to the desired value. We also take into account if the angle wraps around, as to avoid glitches in the animation.

..................Content has been hidden....................

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