Donnerstag, 8. Dezember 2011

A camera in 3D space

In the last couple of days I got crazy over implementing a camera, better to say its motion and rotation in our course project.
Normally it is real simple stuff and you will find it all over the place. I have tried reading some of them but didn't get quiet along.



The camera data comes from assimp (http://assimp.sourceforge.net/).
It has three vectors:
  1. eye [position of the camera]
  2. center [point where the camera is looking at (from (0,0,0))]
  3. up [above; kind of local Y]
There is a transformation matrix hidden in the nodes of the scene. That must be build and applied on those vectors.
I also created some variables for local axis in world space. That way it's easier to apply rotation and translation.

The following stuff is possible:
Roll   -- rotate around local Z axis
Yaw   -- rotate around local Y axis
Pitch -- rotate around local X axis
Translate -- move on local X-Y-Z axis

In old OpenGL you had a modelViewProjection matrix.
In new OpenGL you need to handle them yourself.
That is kind of easy. Especially as the viewMatrix and the projectionMatrix will be hold and updated inside the camera class. (You need to keep in mind that the actual multiplication is "projectionMatrix * viewMatrix * modelMatrix" to get the right results later.)
The most math parts are handled by glm (http://glm.g-truc.net/).
It can e.g create the projectionMatrix (glm::perspective) and the viewMatrix (glm::lookAt) from given values.


Now here is the code:
( colored: http://paste.kde.org/156740/ )


/*
    Simple camera class with motion and rotation.
    Copyright (C) 2011  Manuel Bellersen BellersenM@googlemail.com

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/


#ifndef CCAMERA_H
#define CCAMERA_H

#include "headers_with_gcc_warnings/glm.hpp"
#include "../libraries/glm/gtc/type_ptr.hpp"
#include <string>
#include <cstdint>
#include "clogerrorstream.h"

class aiNode;
struct aiCamera;
class CCamera {

public:
    CCamera();
    CCamera( const CCamera&);
    void operator=( const CCamera&);
    CCamera( uint64_t id, aiCamera* camera);
    virtual ~CCamera();
    void translate( glm::vec3& translateVector);
    void translate( glm::vec3&& translateVector);
    const glm::core::type::mat4 getViewMatrix() const;
    std::string getName();
    uint64_t getID();
   
    /// Get aiCamera to set near-far, FOV, aspect, ...
    aiCamera* getCamera();
   
    void init( aiNode* root);
    const glm::core::type::mat4& getProjectionMatrix();
    const glm::mat4& getProjectionViewMatrix();
    void pitch( float angle);
    void roll( float angle);
    void yaw( float angle);
    void pitchYawRoll( float pitchAngle, float yawAngle, float rollAngle);
    void pitchYawRoll( glm::vec3& pyaAngles);
    void pitchYawRoll( glm::vec3&& pyaAngles);
   
private:
    CLogErrorStream m_logError;
    // The view matrix, which can also be known as the world matrix determines the position of the ‘camera’ in space.
    glm::mat4 m_viewMatrix;
    glm::mat4 m_modelMatrix;
    glm::mat4 m_projectionMatrix;
    glm::mat4 m_projectionViewMatrix;
    glm::vec3 m_eyeGlobal;        // position of camera
    glm::vec3 m_centerGlobal;    // where camera is looking to
    glm::vec3 m_upGlobal;
   
    glm::vec3 m_xGlob;
    glm::vec3 m_yGlob;
    glm::vec3 m_zGlob;
   
    aiCamera* m_camera;
    uint64_t m_ID;
   
    void updateViewMatrix();
    void updateGlobals();
    void updateAxis();
};

#endif // CCAMERA_H

/*
    Simple camera class with motion and rotation.
    Copyright (C) 2011  Manuel Bellersen BellersenM@googlemail.com

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/


#include "ccamera.h"
#include "../libraries/glm/gtc/matrix_transform.hpp"
#include "../libraries/glm/gtx/rotate_vector.hpp"
#include "headers_with_gcc_warnings/aiCamera.h"
#include "headers_with_gcc_warnings/aiScene.h"
#include "aiToGLM.cpp"

CCamera::CCamera():
 m_logError(),
 m_viewMatrix( 1.0f),
 m_modelMatrix( 1.0f),
 m_projectionMatrix( 1.0f),
 m_projectionViewMatrix( 1.0f),
 m_eyeGlobal( glm::vec3(1.0f, 0.0f, 0.0f)),
 m_centerGlobal( glm::vec3(0.0f, 0.0f, 0.0f)),
 m_upGlobal( glm::vec3(0.0f, 1.0f, 0.0f)),
 m_xGlob(0),
 m_yGlob(0),
 m_zGlob(0),
 m_camera( nullptr),
 m_ID( 0)
{
    m_modelMatrix = glm::translate( m_modelMatrix, glm::vec3( 0.0f, 0.0f, -1.0f));
}

CCamera::CCamera(uint64_t id, aiCamera* camera):
 m_logError(),
 m_viewMatrix( 1.0f),
 m_modelMatrix(),
 m_projectionMatrix( 1.0f),
 m_projectionViewMatrix( 1.0f),
 m_eyeGlobal( glm::vec3( camera->mLookAt.x, camera->mLookAt.y, camera->mLookAt.z)),
 m_centerGlobal( glm::vec3( camera->mPosition.x, camera->mPosition.y, camera->mPosition.z)),
 m_upGlobal(glm::vec3( camera->mUp.x, camera->mUp.y, camera->mUp.z )),
 m_xGlob(0),
 m_yGlob(0),
 m_zGlob(0),
 m_camera( camera),
 m_ID(id)
{
   
}

CCamera::~CCamera() {

}

void CCamera::translate(glm::core::type::vec3& translateVector)
{
    glm::vec3 l_z = translateVector.z*glm::normalize(m_zGlob);
    glm::vec3 l_y = translateVector.y*glm::normalize(m_yGlob);
    glm::vec3 l_x = translateVector.x*glm::normalize(m_xGlob);
   
    glm::vec3 t0 = l_z + l_y + l_x;
   
    m_eyeGlobal += t0;
    m_centerGlobal += t0;
   
    updateViewMatrix();
    updateAxis();
}

void CCamera::translate(glm::core::type::vec3&& translateVector)
{
    glm::vec3 l_z = translateVector.z*glm::normalize(m_zGlob);
    glm::vec3 l_y = translateVector.y*glm::normalize(m_yGlob);
    glm::vec3 l_x = translateVector.x*glm::normalize(m_xGlob);
   
    glm::vec3 t0 = l_z + l_y + l_x;
   
    m_eyeGlobal += t0;
    m_centerGlobal += t0;
   
    updateViewMatrix();
    updateAxis();
}

void CCamera::updateViewMatrix()
{
    m_viewMatrix = glm::lookAt( m_eyeGlobal, m_centerGlobal, m_upGlobal);
    m_projectionViewMatrix = m_projectionMatrix*m_viewMatrix;
}

const glm::core::type::mat4 CCamera::getViewMatrix() const
{
    return m_viewMatrix;
}

uint64_t CCamera::getID()
{
    return m_ID;
}

std::string CCamera::getName()
{
    return m_camera->mName.data;
}

aiCamera* CCamera::getCamera()
{
    return m_camera;
}

const glm::core::type::mat4& CCamera::getProjectionMatrix()
{
    return m_projectionMatrix;
}

const glm::core::type::mat4& CCamera::getProjectionViewMatrix()
{
    return m_projectionViewMatrix;
}

void CCamera::init(aiNode* root)
{
    // Create Projection Matrix
    m_projectionMatrix = glm::perspective( glm::degrees(m_camera->mHorizontalFOV), m_camera->mAspect, m_camera->mClipPlaneNear, m_camera->mClipPlaneFar);
   
   
    // Create Model Matrix
   
    //     // Get the camera matrix for a camera at a specific time
    //     // if the node hierarchy for the camera does not contain
    //     // at least one animated node this is a static computation
    //     get-camera-matrix (node sceneRoot, camera cam) : matrix
    //     {
    //         node   cnd = find-node-for-camera(cam)
    //         matrix cmt = identity()
    //        
    //         // as usual - get the absolute camera transformation for this frame
    //         for each node nd in hierarchy from sceneRoot to cnd
    //             matrix cur
    //             if (is-animated(nd))
    //                 cur = eval-animation(nd)
    //             else cur = nd->mTransformation;
    //                 cmt = mult-matrices( cmt, cur )
    //         end for
    //        
    //         // now multiply with the camera's own local transform
    //         cam = mult-matrices (cam, get-camera-matrix(cmt) )
    //     }
   
    aiNode* myNode = root->FindNode( m_camera->mName);
    if( myNode == nullptr){
        m_modelMatrix = glm::mat4(1);
    }
    aiMatrix4x4 m4;
    while( myNode != root){
         m4 = myNode->mTransformation * m4;
         myNode = myNode->mParent;
    }
   
    aiMatrix4x4 aim;
    m_camera->GetCameraMatrix(aim);
    m4 = aim * (myNode->mTransformation * m4);
   
    m_modelMatrix = aiToGLMM4(m4);
    print(m_modelMatrix, m_logError.logStream());
   
    updateGlobals();
    updateAxis();
   
    updateViewMatrix();
}

void CCamera::updateGlobals()
{
    glm::vec4 eG = m_modelMatrix*glm::vec4( m_eyeGlobal, 0.0f);
    m_eyeGlobal = glm::vec3( eG.x, eG.y, eG.z);
   
    glm::vec4 cG = m_modelMatrix*glm::vec4( m_centerGlobal, 0.0f);
    m_centerGlobal = glm::vec3( cG.x, cG.y, cG.z);
   
    glm::vec4 uG = m_modelMatrix*glm::vec4( m_upGlobal, 0.0f);
    m_upGlobal = glm::vec3( uG.x, uG.y, uG.z);
}

void CCamera::updateAxis()
{
    m_yGlob = m_upGlobal;
    m_zGlob = m_centerGlobal - m_eyeGlobal;
    m_xGlob = glm::cross(m_yGlob, m_zGlob);
}

// TODO: use quaternions for rotation (but works already fine)
void CCamera::pitchYawRoll(float pitchAngle, float yawAngle, float rollAngle)
{
    pitch( pitchAngle);
    yaw(yawAngle);
    roll(rollAngle);
}

void CCamera::pitchYawRoll(glm::core::type::vec3& pyaAngles)
{
    pitch( pyaAngles.x);
    yaw( pyaAngles.y);
    roll( pyaAngles.z);
}

void CCamera::pitchYawRoll(glm::core::type::vec3&& pyaAngles)
{
    pitch( pyaAngles.x);
    yaw( pyaAngles.y);
    roll( pyaAngles.z);
}

void CCamera::roll(float angle) // local z
{
    glm::vec3 diffRotZ = glm::rotate( m_yGlob, angle, m_zGlob) - m_yGlob;
    m_upGlobal += diffRotZ;
    updateViewMatrix();
    updateAxis();
}

void CCamera::pitch(float angle) // local X
{
    glm::vec3 diffCenter = glm::rotate( m_zGlob, -angle, m_xGlob) - m_zGlob;
    glm::vec3 diffUp = glm::rotate( m_yGlob, -angle, m_xGlob) - m_yGlob;
    m_centerGlobal += diffCenter;
    m_upGlobal += diffUp;
    updateViewMatrix();
    updateAxis();
}

void CCamera::yaw(float angle) // local Y (up)
{
    glm::vec3 diffLR = glm::rotate( m_zGlob, -angle, m_yGlob) - m_zGlob;
    m_centerGlobal += diffLR;
    updateViewMatrix();
    updateAxis();
}

2 Kommentare:

  1. Hi

    nice tutorial.

    Windows have a command for grabbing mouse position:

    GetMousePos();

    Is any equivalent on Linux ? So simple without SDL library ?

    Thank You

    Stanlez

    AntwortenLöschen
    Antworten
    1. Thanks.

      You could use xlib:
      http://ubuntuforums.org/showpost.php?p=4110065&postcount=7
      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      Display *dsp = XOpenDisplay( NULL );
      if( !dsp ){ return 1; }

      int screenNumber = DefaultScreen(dsp);

      XEvent event;

      /* get info about current pointer position */
      XQueryPointer(dsp, RootWindow(dsp, DefaultScreen(dsp)),
      &event.xbutton.root, &event.xbutton.window,
      &event.xbutton.x_root, &event.xbutton.y_root,
      &event.xbutton.x, &event.xbutton.y,
      &event.xbutton.state);

      printf("Mouse Coordinates: %d %d\n", event.xbutton.x, event.xbutton.y);

      XCloseDisplay( dsp );
      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      More about it:
      http://users.actcom.co.il/~choo/lupg/tutorials/xlib-programming/xlib-programming.html
      http://www.sbin.org/doc/Xlib/
      http://tronche.com/gui/x/xlib/

      especially XQueryPointer:
      http://tronche.com/gui/x/xlib/window-information/XQueryPointer.html


      But then your code is stuck to Linux. That is not nice when porting it to other operating systems or even on Linux when trying a different graphics server like Wayland.

      I always try to use some abstraction library like Qt, SDL, or SFML to have my code as portable as it could be.

      (On Windows you use the Windows-library to have access to those functions. On Linux there are more choices. You can go low-level and access /dev/input/*, use ncurses for terminal, xlib for XServer, something for Wayland, or move higher to libs like SFML, SDL, Qt. The higher you go the simpler it will be.)


      Hope that helped.

      Have fun!

      Löschen

[Review/Critic] UDock X - 13,3" LapDock

The UDock X - 13.3" LapDock is a combination of touch display, keyboard, touch-pad and battery. It can be used as an extension of vari...