Picking in 3D

In this recipe, we will calculate the intersection of the mouse cursor with a 3D model.

Getting ready

Include the necessary files to draw using OpenGL, use textures and load images, load 3D models, define OpenGL lights and materials, and use Cinder's Maya camera.

#include "cinder/gl/gl.h"
#include "cinder/gl/Texture.h"
#include "cinder/gl/Light.h"
#include "cinder/gl/Material.h"
#include "cinder/TriMesh.h"
#include "cinder/ImageIo.h"
#include "cinder/MayaCamUI.h"

Also, add the following using statements:

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

We will use a 3D model, so place a file and its texture in the assets folder. For this example, we will be using a mesh file named ducky.msh and a texture named ducky.png.

How to do it…

  1. We will use the ci::CameraPersp and ci::Ray classes to convert the mouse coordinates to our rotated 3D scene and calculate the intersection with a 3D model.
  2. Declare the members to define the 3D model and its intersection with the mouse, as well as a ci::MayaCamUI object for easy navigation, and a ci::gl::Material for lighting:
    TriMesh mMesh;
    gl::Texture mTexture;
    MayaCamUI mCam;
    bool mIntersects;
    Vec3f mNormal, mHitPos;
    AxisAlignedBox3f mMeshBounds;
    gl::Material mMaterial;
  3. Declare a method where we will calculate the intersection between a ci::Ray class and the triangles that make up mMesh.
    void calcIntersectionWithMeshTriangles( const ci::Ray& ray );
  4. In the setup method, lets load the model and texture and calculate its bounding box:
    mMesh.read( loadAsset( "ducky.msh" ) );
    mTexture = loadImage( loadAsset( "ducky.png" ) );
    mMeshBounds = mMesh.calcBoundingBox();
  5. Let's define the camera and make it look as if it's at the center of the model. Add the following code in the setup method:
    CameraPersp cam;
    Vec3f modelCenter = mMeshBounds.getCenter();
    cam.setEyePoint( modelCenter + Vec3f( 0.0f, 0.0f, 20.0f ) );
    cam.setCenterOfInterestPoint( modelCenter );
    mCam.setCurrentCam( cam );
  6. Finally, set up the material for the model's lighting.
    mMaterial.setAmbient( Color::black() );
    mMaterial.setDiffuse( Color::white() );
    mMaterial.setEmission( Color::black() );
  7. Declare the handlers for the mouseDown and mouseDrag events.
    void mouseDown( MouseEvent event );
    void mouseDrag( MouseEvent event );
  8. Implement these methods by calling the necessary methods of mCam:
    void MyApp::mouseDown( MouseEvent event ){
      mCam.mouseDown( event.getPos() );
    }
    
    void MyApp::mouseDrag( MouseEvent event ){
      mCam.mouseDrag( event.getPos(), event.isLeftDown(), event.isMiddleDown(), event.isRightDown() );
    }
  9. Let's implement the update method and calculate the intersection between the mouse cursor and our model. Let's begin by getting the mouse position and then calculate ci::Ray emitting from our camera:
    Vec2f mousePos = getMousePos();
    float u = mousePos.x / (float)getWindowWidth();
    float v = mousePos.y / (float)getWindowHeight();
    CameraPersp cameraPersp = mCam.getCamera();
    Ray ray = cameraPersp.generateRay( u, 1.0f - v, cameraPersp.getAspectRatio() );
  10. Let's perform a fast test and check if the ray intersects with the model's bounding box. If the result is true, we will call the calcIntersectionWithMeshTriangles method.
        if( mMeshBounds.intersects( ray ) == false ){
      mIntersects = false;
        } else {
      calcIntersectionWithMeshTriangles( ray );
        }
  11. Let's implement the calcIntersectionWithMeshTriangles method. We will iterate over all the triangles of our model and calculate the nearest intersection and store its index.
    float distance = 0.0f;
    float resultDistance = 999999999.9f;
    int resultIndex = -1;
    int numTriangles = mMesh.getNumTriangles();
    for( int i=0; i<numTriangles; i++ ){
            Vec3f v1, v2, v3;
            mMesh.getTriangleVertices( i, &v1, &v2, &v3 );
            if( ray.calcTriangleIntersection( v1, v2, v3, &distance ) ){
            if( distance <resultDistance ){
            resultDistance = distance;
            resultIndex = i;
                }
            }
        }
  12. Let's check if there was any intersection and calculate its position and normal. If no intersection was found, we will simply set mIntersects to false.
    if( resultIndex> -1 ){
            mHitPos = ray.calcPosition( resultDistance );
            mIntersects = true;
            Vec3f v1, v2, v3;
            mMesh.getTriangleVertices( resultIndex, &v1, &v2, &v3 );
            mNormal = ( v2 - v1 ).cross( v3 - v1 );
            mNormal.normalize();
        } else {
          mIntersects = false;
        }
  13. With the intersection calculated, let's draw the model, intersection point, and normal. Start by clearing the background with black, setting the window's matrices using our camera, and enabling reading and writing to the depth buffer. Add the following code in the draw method:
    gl::clear( Color( 0, 0, 0 ) ); 
    gl::setMatrices( mCam.getCamera() );
    gl::enableDepthRead();
    gl::enableDepthWrite();
  14. Now let's create a light and set its position as the camera's eye position. We'll also enable the light and apply the material.
    gl::Light light( gl::Light::POINT, 0 );
    light.setPosition( mCam.getCamera().getEyePoint() );
    light.setAttenuation( 1.0f, 0.0f, 0.0f );
    glEnable( GL_LIGHTING );
    light.enable();
    mMaterial.apply();
  15. Now enable and bind the models texture, draw the model, and disable both texture and lighting.
    mTexture.enableAndBind();
    gl::draw( mMesh );
    mTexture.unbind();
    glDisable( GL_LIGHTING ); 
  16. Finally, we will check if mIntersects is true and draw a sphere at the intersection point and the normal vector.
    if( mIntersects ){
      gl::color( Color::white() );
      gl::drawSphere( mHitPos, 0.2f );
      gl::drawVector( mHitPos, mHitPos + ( mNormal * 2.0f ) );
        }
    How to do it…

How it works…

To calculate the intersection of the mouse with the model in 3D, we generated a ray from the mouse position towards the view direction of the camera.

For performance reasons, we first calculate if the ray intersects with the model's bounding box. In case there is an intersection with the model, we further calculate the intersection between the ray and each triangle that makes up the model. For every intersection found, we check its distance and calculate the intersection point and the normal of only the nearest intersection.

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

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