Drawing in 3D with the mouse

In this recipe, we will draw with the mouse on a 3D space. We will draw lines when dragging the mouse or rotate the scene in 3D when dragging and pressing the Shift key simultaneously.

Getting ready

Include the necessary files to draw using OpenGL, as well as the files needed to use Cinder's perspective, Maya camera, and poly lines.

#include "cinder/gl/gl.h"
#include "cinder/Camera.h"
#include "cinder/MayaCamUI.h"
#include "cinder/PolyLine.h"

Also, add the following using statements:

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

How to do it…

We will use the ci::CameraPersp and ci::Ray classes to convert the mouse coordinates to our rotated 3D scene.

  1. Declare a ci::MayaCamUI object and a std::vector object of ci::PolyLine<ci::Vec3f> to store the drawn lines:
    MayaCamUI mCamera;
    vector<PolyLine<Vec3f> > mLines;
  2. In the setup method, we will create ci::CameraPersp and set it up so that the point of interest is the center of the window. We will also set the camera as the current camera of mCamera:
    CameraPersp cameraPersp( getWindowWidth(),getWindowHeight(), 60.0f );
    Vec3f center( getWindowWidth() / 2, getWindowHeight() / 2,0.0f );
    cameraPersp.setCenterOfInterestPoint( center );
    mCamera.setCurrentCam( cameraPersp );
  3. In the draw method, let's clear the background with black and use our camera to set the window's matrices.
      gl::clear( Color( 0, 0, 0 ) ); 
    gl::setMatrices( mCamera.getCamera() );
  4. Now let's iterate mLines and draw each ci::PolyLine. Add the following code to the draw method:
    for( vector<PolyLine<Vec3f> > ::iterator it = mLines.begin(); it != mLines.end(); ++it ){
    gl::draw( *it );
        }
  5. With our scene set up and the lines being drawn, we need to create the 3D perspective! Let's start by declaring a method to convert coordinates from the screen position to world position. Add the following method declaration:
        Vec3f screenToWorld( const Vec2f&point ) const;
  6. In the screenToWorld implementation, we need to generate a ray from point using the cameras perspective. Add the following code in screenToWorld:
    float u = point.x / (float)getWindowWidth();
    float v = point.y / (float)getWindowHeight();
    
    const CameraPersp& cameraPersp = mCamera.getCamera();
    
    Ray ray = cameraPersp.generateRay( u, 1.0f - v, cameraPersp.getAspectRatio() );
  7. Now we need to calculate where the ray will intersect with a perpendicular plane at the camera's center of interest and then return the intersection point. Add the following code in the screenToWorld implementation:
    float result = 0.0f;
    Vec3f planePos = cameraPersp.getCenterOfInterestPoint();
    Vec3f normal = cameraPersp.getViewDirection();
    
    ray.calcPlaneIntersection( planePos, normal, &result );
    
    Vec3f intersection= ray.calcPosition( result );
    return intersection;
  8. Let's use the previously defined method to draw with the mouse. Declare the mouseDown and mouseDrag event handlers:
    void mouseDown( MouseEvent event );
    void mouseDrag( MouseEvent event );
  9. In the implementation of mouseDown, we will check if the Shift key is being pressed. If it is, we will call the mouseDown method of mCamera, otherwise, we will add ci::PolyLine<ci::Vec3f> to mLines, calculate the world position of the mouse cursor using screenToWorld, and add it:
    void MyApp::mouseDown( MouseEvent event ){
      if( event.isShiftDown() ){
      mCamera.mouseDown( event.getPos() );
        }
    else {    
            mLines.push_back( PolyLine<Vec3f>() );
            Vec3f point = screenToWorld( event.getPos() );
            mLines.back().push_back( point );
        }
    }
  10. In the implementation of mouseDrag, we will check if the Shift key is being pressed. If it is, we will call the mouseDrag method to mCamera, otherwise, we will calculate the world position of the mouse cursor and add it to last line in mLines.
    void Pick3dApp::mouseDrag( MouseEvent event ){
        if( event.isShiftDown() ){
        mCamera.mouseDrag( event.getPos(), event.isLeftDown(), event.isMiddleDown(), event.isRightDown() );
        } else {
            Vec3f point = screenToWorld( event.getPos() );
            mLines.back().push_back( point );
        }
    }
  11. Build and run the application. Press and drag the mouse to draw a line. Press the Shift key and press and drag the mouse to rotate the scene.

How it works…

We use ci::MayaCamUI to easily rotate our scene.

The ci::Ray class is a representation of a ray, containing an origin, direction, and an infinite length. It provides useful methods to calculate intersections between rays and triangles or planes.

To calculate the world position of the mouse cursor we calculated a ray going from the camera's eye position in the camera's view direction.

We then calculated the intersection of the ray with the plane at the center of the scene, perpendicular to the camera.

The calculated position is then added to a ci::PolyLine<ci::Vec3f> object to draw the lines.

See also

  • To learn more on how to use ci::MayaCamUI, please refer to the recipe Using MayaCamUI from Chapter 2, Preparing for Development.
  • To learn how to draw in 2D, please read the recipe Drawing arbitrary shapes with the mouse from Chapter 7, Using 2D Graphics.
..................Content has been hidden....................

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