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.
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;
We will use the ci::CameraPersp
and ci::Ray
classes to convert the mouse coordinates to our rotated 3D scene.
ci::MayaCamUI
object and a std::vector
object of ci::PolyLine<ci::Vec3f>
to store the drawn lines:MayaCamUI mCamera; vector<PolyLine<Vec3f> > mLines;
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 );
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() );
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 ); }
Vec3f screenToWorld( const Vec2f&point ) const;
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() );
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;
mouseDown
and mouseDrag
event handlers:void mouseDown( MouseEvent event ); void mouseDrag( MouseEvent event );
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 ); } }
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 ); } }
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.
3.15.137.75