2
votes

my goal is to make a camera with OpenGL and SDL 1.2 in the C language. I want to be able to move in all the directions (forward, backward, left, right, up down). I want to be able to rotate freely the camera in all the direction : up down left right (like in space with a spacecraft). In this first version, I just use the keyboard but in the future, I want to be able to move the direction of the camera with the mouse and the spacecraft with the keyboard.

Here is the full code I wrote/recover.

#include <stdio.h>
#include <stdlib.h>
#include <SDL/SDL.h>
#include <GL/gl.h>
#include <GL/glu.h>
#include <math.h>

#define WINDOWS_WIDTH 1280.0
#define WINDOWS_HEIGHT 720.0

#define SPEED_MOVE 0.1
#define SPEED_CAMERA 0.005

typedef struct strucvect{
    double x;
    double y;
    double z;
}vect;

double phi=0, theta=0;

vect position, orientation, lateral, vertical, target;

int continuing=1;

vect scaleVector(vect v){
    v.x *= SPEED_MOVE;
    v.y *= SPEED_MOVE;
    v.z *= SPEED_MOVE;
    return v;
}

vect unitVector(vect v){
    double norm = sqrt(v.x*v.x + v.y*v.y + v.z*v.z);
    v.x = v.x / norm;
    v.y = v.y / norm;
    v.z = v.z / norm;
    return v;
}
vect productVector(vect v1 ,vect v2){
    vect new;
    new.x = v1.y * v2.z - v1.z * v2.y;
    new.y = v1.z * v2.x - v1.x * v2.z;
    new.z = v1.x * v2.y - v1.y * v2.x;
    return new ;
}
vect addVector(vect v1 ,vect v2){
    v1.x += v2.x;
    v1.y += v2.y;
    v1.z += v2.z;
    return v1;
}
vect subVector(vect v1 ,vect v2){
    v1.x -= v2.x;
    v1.y -= v2.y;
    v1.z -= v2.z;
    return v1;
}

void computeOrientation(){
    orientation.x = cos(phi) * sin(theta); 
    orientation.y = cos(phi) * cos(theta);
    orientation.z = sin(phi);
}

void manageKeys(Uint8 *keys){
    if(keys[SDLK_ESCAPE] == SDL_PRESSED){
        continuing = 0;
        return;
    }
    if(keys[SDLK_w] == SDL_PRESSED) // move forward
        position = addVector(position,scaleVector(orientation));
    if(keys[SDLK_s] == SDL_PRESSED) // move backward
        position = subVector(position,scaleVector(orientation));
    if(keys[SDLK_a] == SDL_PRESSED){ // moveleft
        lateral = unitVector(productVector(vertical,orientation));
        position = addVector(position,scaleVector(lateral));
    }
    if(keys[SDLK_d] == SDL_PRESSED){ // move right
        lateral = unitVector(productVector(vertical,orientation));
        position = subVector(position,scaleVector(lateral));
    }
    if(keys[SDLK_SPACE] == SDL_PRESSED){ // move up
        vertical =  unitVector(productVector(orientation,lateral));
        position = addVector(position,scaleVector(vertical));
    }
    if(keys[SDLK_q] == SDL_PRESSED){ // move bottom
        vertical = unitVector(productVector(orientation,lateral));
        position = subVector(position,scaleVector(vertical));
    }
    if(keys[SDLK_z] == SDL_PRESSED){ // turn the camera to look at the right
        theta += SPEED_CAMERA;
        if(theta > 6.28318530)
            theta = 0;
        computeOrientation();
        lateral = unitVector(productVector(vertical,orientation));
    }
    if(keys[SDLK_x] == SDL_PRESSED){ // turn the camera to look at the left
        theta -= SPEED_CAMERA;
        if(theta < -6.28318530)
            theta = 0;
        computeOrientation();
        lateral = unitVector(productVector(vertical,orientation));
    }
    if(keys[SDLK_c] == SDL_PRESSED){ // turn the camera to look up
        phi += SPEED_CAMERA;
        if(phi > 6.28318530)
            phi = 0;
        computeOrientation();   
        vertical = unitVector(productVector(orientation,lateral));
    }
    if(keys[SDLK_v] == SDL_PRESSED){ // turn the camera to look bottom
        phi -= SPEED_CAMERA;
        if(phi < -6.28318530)
            phi = 0;
        computeOrientation();   
        vertical = unitVector(productVector(orientation,lateral));
    }
}

void display(){
    double ratio= WINDOWS_WIDTH/WINDOWS_HEIGHT;

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

    gluPerspective(70,ratio,1,2000);
    target = addVector(position ,orientation); // we look a point in front of us : in the direction of the orientation vector
    gluLookAt(position.x, position.y, position.z,  target.x,target.y,target.z,   vertical.x,vertical.y,vertical.z);

    glBegin(GL_QUADS); // we draw the 6 face of a cube
    // bottom wall
    glColor3f(0.1, 0.1, 0.6);
    glVertex3f(-10,-10,-10);
    glVertex3f(-10,10,-10);
    glVertex3f(10,10,-10);
    glVertex3f(10,-10,-10); 
    // up wall
    glColor3f(0.6, 0.1, 0.6);
    glVertex3f(-10,-10,10);
    glVertex3f(-10,10,10);
    glVertex3f(10,10,10);
    glVertex3f(10,-10,10);      
    // right wall
    glColor3f(0.6, 0.1, 0.1);
    glVertex3f(-10,-10,-10);
    glVertex3f(-10,-10,10);
    glVertex3f(10,-10,10);
    glVertex3f(10,-10,-10);
    // left wall
    glColor3f(0.6, 0.6, 0.1);
    glVertex3f(-10,10,-10);
    glVertex3f(-10,10,10);
    glVertex3f(10,10,10);
    glVertex3f(10,10,-10);      
    // front wall
    glColor3f(0.1, 0.6, 0.1);
    glVertex3f(-10,-10,-10);
    glVertex3f(-10,-10,10);
    glVertex3f(-10,10,10);
    glVertex3f(-10,10,-10);
    // behind wall
    glColor3f(0.1, 0.6, 0.6);
    glVertex3f(10,-10,-10);
    glVertex3f(10,-10,10);
    glVertex3f(10,10,10);
    glVertex3f(10,10,-10);      
    glEnd();

    glFlush();
    SDL_GL_SwapBuffers();
}

int main(int argc, char* argv[]){
    SDL_Event event;    
    SDL_Init(SDL_INIT_VIDEO);
    SDL_WM_SetCaption("Camera",NULL);
    SDL_SetVideoMode(WINDOWS_WIDTH, WINDOWS_HEIGHT, 32, SDL_OPENGL);

    glEnable(GL_DEPTH_TEST);

    position.x = position.y = position.z = 0; // we are in the middle of the cube

    orientation.x = cos(phi) * sin(theta); 
    orientation.y = cos(phi) * cos(theta);
    orientation.z = sin(phi);

    vertical.x = 0;
    vertical.y = 0;
    vertical.z = 1;

    lateral = unitVector(productVector(vertical,orientation));

    while (continuing){
        SDL_PollEvent(&event);
        switch(event.type){
        case SDL_QUIT:
            continuing = 0;
            break;
        }
        manageKeys(SDL_GetKeyState(NULL));
        display();
    }
    SDL_Quit();
    return EXIT_SUCCESS;
}

And the command I use to compile:

gcc -Wall -lGL -lGLU -lm -lSDL main.c -o main

In the program, I draw a cube with a different color for the 6 face and I place the camera in the middle of the cube.

I made the move of the camera work with these keys w(forward) a(left) s(backward) d(right) space(up) q(down)

I can rotate in one of the two direction with no problem (with z(right) and x(left) or c(up) and v(down)). I can even move the camera horizontally and then vertically with no problem.

The issues are with first a vertical rotation and then a horizontal.

  • if I rotate with c or v key at 180°, then the rotation with the horizontal axis are inverted and I don't want it.
  • if I rotate with c or v key at 90°, then the rotation with the horizontal axis seems to do nothing and the camera is stuck and I don't want it

I read some things over the internet talking about an issue called gimbal lock with Euler angles, but I don't know if this is my issue. I also read about camera with quaternions and I plan to use this method if I can't manage to made this first one work.

Can you tell me what are my issue about and how to resolve it ?

1
as far as i'm concerned , i had this problem, and had to use my own personal matrix class to be able to combine the transformations in the correct way, generally speaking, you shouldnt use gluLookAt (well that's just what I think )when you want to do things like these, because the parameters it use make no sense when you want to implement a free camera - Guiroux

1 Answers

2
votes

I implanted the camera with quaternion (I used this website for the formula ). I have all I wanted : move in 6 directions, and rotation of the camera up/down and left/right. I can even roll left/right the camera.

Here is the full code:

main.c

#include <stdio.h>
#include <stdlib.h>
#include <SDL/SDL.h>
#include <GL/gl.h>
#include <GL/glu.h>
#include "camera.h"

#define WINDOWS_WIDTH 1280.0
#define WINDOWS_HEIGHT 720.0

#define SPEED_MOVE 0.1
#define SPEED_CAMERA 0.005

vector position, orientation, lateral, vertical, target;

int continuing=1;

void manageKeys(Uint8 *keys){
    if(keys[SDLK_ESCAPE] == SDL_PRESSED){
        continuing = 0;
        return;
    }

    if(keys[SDLK_w] == SDL_PRESSED) // move forward
      position = addVector(position, scaleVector(orientation, SPEED_MOVE));
    if(keys[SDLK_s] == SDL_PRESSED) // move backward
      position = subVector(position, scaleVector(orientation, SPEED_MOVE));
    if(keys[SDLK_a] == SDL_PRESSED) // move left
      position = addVector(position, scaleVector(lateral, SPEED_MOVE));
    if(keys[SDLK_d] == SDL_PRESSED) // move right
      position = subVector(position, scaleVector(lateral, SPEED_MOVE));
    if(keys[SDLK_SPACE] == SDL_PRESSED) // move up
      position = addVector(position, scaleVector(vertical, SPEED_MOVE));
    if(keys[SDLK_q] == SDL_PRESSED) // move bottom
      position = subVector(position, scaleVector(vertical, SPEED_MOVE));


    if(keys[SDLK_z] == SDL_PRESSED){ // turn the camera to look at the left
      orientation = makeRotation(orientation, vertical, SPEED_CAMERA);
      lateral = unitVector(productVector(vertical,orientation));
    }
    if(keys[SDLK_x] == SDL_PRESSED){ // turn the camera to look at the right
      orientation = makeRotation(orientation, vertical, -SPEED_CAMERA);
      lateral = unitVector(productVector(vertical,orientation));
    }
    if(keys[SDLK_c] == SDL_PRESSED){ // turn the camera to look up
      orientation = makeRotation(orientation, lateral, -SPEED_CAMERA);
      vertical = unitVector(productVector(orientation,lateral));
    }
    if(keys[SDLK_v] == SDL_PRESSED){ // turn the camera to look bottom
      orientation = makeRotation(orientation, lateral, SPEED_CAMERA);
      vertical = unitVector(productVector(orientation,lateral));        
    }
    if(keys[SDLK_f] == SDL_PRESSED){// turn the camera with a roll to the left
      lateral = makeRotation(lateral, orientation, -SPEED_CAMERA);
      vertical = unitVector(productVector(orientation,lateral));
    }
    if(keys[SDLK_g] == SDL_PRESSED){// turn the camera with a roll to the right
      lateral = makeRotation(lateral, orientation, SPEED_CAMERA);
      vertical = unitVector(productVector(orientation,lateral));        
    }
}

void display(){
    double ratio= WINDOWS_WIDTH/WINDOWS_HEIGHT;

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

    gluPerspective(70,ratio,1,2000);

    target = addVector(position, orientation);
    gluLookAt(position.x, position.y, position.z,      target.x,target.y,target.z,     vertical.x,vertical.y,vertical.z);

    glBegin(GL_QUADS);
    // bottom wall
    glColor3f(0.1, 0.1, 0.6);
    glVertex3f(-10,-10,-10);
    glVertex3f(-10,10,-10);
    glVertex3f(10,10,-10);
    glVertex3f(10,-10,-10); 
    // up wall
    glColor3f(0.6, 0.1, 0.6);
    glVertex3f(-10,-10,10);
    glVertex3f(-10,10,10);
    glVertex3f(10,10,10);
    glVertex3f(10,-10,10);      
    // right wall
    glColor3f(0.6, 0.1, 0.1);
    glVertex3f(-10,-10,-10);
    glVertex3f(-10,-10,10);
    glVertex3f(10,-10,10);
    glVertex3f(10,-10,-10);
    // left wall
    glColor3f(0.6, 0.6, 0.1);
    glVertex3f(-10,10,-10);
    glVertex3f(-10,10,10);
    glVertex3f(10,10,10);
    glVertex3f(10,10,-10);      
    // front wall
    glColor3f(0.1, 0.6, 0.1);
    glVertex3f(-10,-10,-10);
    glVertex3f(-10,-10,10);
    glVertex3f(-10,10,10);
    glVertex3f(-10,10,-10);
    // behind wall
    glColor3f(0.1, 0.6, 0.6);
    glVertex3f(10,-10,-10);
    glVertex3f(10,-10,10);
    glVertex3f(10,10,10);
    glVertex3f(10,10,-10);      
    glEnd();

    glFlush();
    SDL_GL_SwapBuffers();
}

int main(int argc, char* argv[]){
    SDL_Event event;    
    SDL_Init(SDL_INIT_VIDEO);
    SDL_WM_SetCaption("Camera",NULL);
    SDL_SetVideoMode(WINDOWS_WIDTH, WINDOWS_HEIGHT, 32, SDL_OPENGL);

    glEnable(GL_DEPTH_TEST);

    position.x = position.y = position.z = 0; // we are in the middle of the cube

    orientation.x = 1;
    orientation.y = 0;
    orientation.z = 0;

    vertical.x = 0;
    vertical.y = 0;
    vertical.z = 1;

    lateral = unitVector(productVector(vertical,orientation));

    while (continuing){
        SDL_PollEvent(&event);
        switch(event.type){
        case SDL_QUIT:
            continuing = 0;
            break;
        }
        manageKeys(SDL_GetKeyState(NULL));
        display();
    }
    SDL_Quit();
    return EXIT_SUCCESS;
}

camera.h

#ifndef CAMERA
#define CAMERA

#include <math.h>

typedef struct structVector{
    double x, y, z;
}vector;

vector addVector(vector u, vector v);

vector subVector(vector u, vector v);

vector unitVector(vector v);

vector productVector(vector u ,vector v);

vector scaleVector(vector u, double scale);



typedef struct structQuaternion{
    double x, y, z, w;
}quaternion;

quaternion conjugateQuaternion(quaternion a);

quaternion multiplyingQuaternion(quaternion a, quaternion b);

quaternion vector2quaternion(vector v);

quaternion createRotation(vector v, double angle);

vector makeRotation(vector v, vector rotationAxis, double angle);

#endif

camera.c

#include "camera.h"

vector addVector(vector u, vector v){
  u.x += v.x;
  u.y += v.y;
  u.z += v.z;
  return u;
}
vector subVector(vector u, vector v){
  u.x -= v.x;
  u.y -= v.y;
  u.z -= v.z;
  return u;
}
vector unitVector(vector v){
  double norm = sqrt(v.x*v.x + v.y*v.y + v.z*v.z);
  v.x /= norm;
  v.y /= norm;
  v.z /= norm;
  return v;
}
vector productVector(vector u ,vector v){
  vector a;
  a.x = u.y * v.z - u.z * v.y;
  a.y = u.z * v.x - u.x * v.z;
  a.z = u.x * v.y - u.y * v.x;
  return a;
}
vector scaleVector(vector u, double scale){
  u.x *= scale;
  u.y *= scale;
  u.z *= scale;
  return u;
}

quaternion conjugateQuaternion(quaternion a){
  a.x = -a.x;
  a.y = -a.y;
  a.z = -a.z;
  return a;
}
quaternion multiplyingQuaternion(quaternion a, quaternion b){
  quaternion c;
  c.x = a.w*b.x + a.x*b.w + a.y*b.z - a.z*b.y;
  c.y = a.w*b.y - a.x*b.z + a.y*b.w + a.z*b.x;
  c.z = a.w*b.z + a.x*b.y - a.y*b.x + a.z*b.w;
  c.w = a.w*b.w - a.x*b.x - a.y*b.y - a.z*b.z;
  return c;
}
quaternion vector2quaternion(vector v){
  quaternion a;
  a.x = v.x;
  a.y = v.y;
  a.z = v.z;
  a.w = 0;
  return a;
}
quaternion createRotation(vector v, double angle){
  quaternion a;
  a.x = v.x * sin(angle/2);
  a.y = v.y * sin(angle/2);
  a.z = v.z * sin(angle/2);
  a.w = cos(angle/2);
  return a;
}
vector makeRotation(vector v, vector rotationAxis, double angle){
  quaternion rotation = createRotation(rotationAxis, angle);
  quaternion quaternionOfV  = vector2quaternion(v);
  quaternion a = multiplyingQuaternion(rotation, quaternionOfV );
  a = multiplyingQuaternion(a, conjugateQuaternion(rotation));
  v.x = a.x;
  v.y = a.y;
  v.z = a.z;
  return v;
}

I still use the function gluLookAt even if I don't look at something. Maybe there is an other better way to do it.