0
votes

Background

I currently have an environment I have created to which I load some shapes (for reference to where I am) and am trying to get the controls to move forward, backwards, and rotate where I am looking.

I have the forward and backwards worked out using my lookat point and camera position point. I am trying to rotate 360 degrees but appear to get some strange limits based on what I have so far. I am outputting my xangle to see where I am at always..

In order to rotate, I change the lookat variable (x,y, and z) while keeping the position (x,y,z) the same.

Problem

When continuously increasing the angle, I hit two reflection points where the appeared rotation changes direction. For whatever reason these seem to happen at 60 degrees and 300 degrees as shown below:

enter image description here

Obviously these angles are not correct. I have evaluated the behavior that should happen as the rotation happens and the calculated Cartesian coordinates seem to be correct but the display of the angle is what is off.

My setupviewport sub:

Private Sub SetupViewport()
    Dim w As Integer = GLcontrol1.Width
    Dim h As Integer = GLcontrol1.Height

    Dim perspective1 As Matrix4 = cam.GetViewMatrix() * Matrix4.CreatePerspectiveFieldOfView(1.3F, GLcontrol1.Width / CSng(GLcontrol1.Height), 0.1F, 2000.0F)


    GL.MatrixMode(MatrixMode.Projection)
    GL.LoadIdentity()
    GL.Ortho(0, w, h, 0, -1, 1)
    GL.LoadMatrix(perspective1)
    GL.MatrixMode(MatrixMode.Modelview)
    GL.LoadIdentity()
    GL.Viewport(0, 0, w, h)
    GL.Enable(EnableCap.DepthTest)
    GL.DepthFunc(DepthFunction.Less)

End Sub

My camera class:

Class Camera
Public Position As Vector3 = Vector3.Zero
Public Orientation As New Vector3(0.0F, 0.0F, 0.0F)
Public MoveSpeed As Single = 0.2F
Public MouseSensitivity As Single = 0.01F
Public lookat As New Vector3()

Public manual_lookat As Boolean = False

Public invert_y As Boolean = False

Public Function aim_at_origin()
    Position.X = 0
    Position.Y = 0
    Position.Z = 2

    If invert_y = False Then
        Return Matrix4.LookAt(Position, Position + lookat, Vector3.UnitY)
    Else
        Return Matrix4.LookAt(Position, Position + lookat, -Vector3.UnitY)
    End If
End Function




Public Function GetViewMatrix() As Matrix4


    If invert_y = False Then
        Return Matrix4.LookAt(Position, lookat, Vector3.UnitY)
    Else
        Return Matrix4.LookAt(Position, lookat, -Vector3.UnitY)
    End If

End Function

End Class

The camera class establishes the matrix to multiply against the current one. The multiplication happens every frame when setupviewport is called.

I can't figure out why it has the reflection points at 300 and 60 degrees. To me 180 degrees or 360 would make sense. It appears like the area of rotation is a total of 45 degrees from visually looking.

I am tagging this is MATH, C#, and VB .NET as answers can be acceptable in most programming languages.

In order to rotate, I call this class:

Private Sub rotate_view(ByVal delta_camanglex As Single, ByVal delta_camangley As Single)
    Dim curdistance As Single = 1
    curdistance = Math.Sqrt((cam.Position.X - cam.lookat.X) ^ 2 + (cam.Position.Y - cam.lookat.Y) ^ 2 + (cam.Position.Z - cam.lookat.Z) ^ 2)

    Dim invertx As Boolean = False
    Dim inverty As Boolean = False

    camanglex = camanglex + delta_camanglex
    camangley = camangley + delta_camangley

    If camanglex >= 360 Then
        camanglex = camanglex - 360
    End If

    If camangley >= 360 Then
        camangley = camangley - 360
    End If


    If camanglex < 0 Then
        camanglex = camanglex + 360
    End If

    If camangley < 0 Then
        camangley = camangley + 360
    End If

    cam.manual_lookat = True

    Dim sigma As Single = camanglex
    Dim theda As Single = camangley

    lookatx = curdistance * Sin(sigma * (PI / 180)) * Cos((theda) * (PI / 180))



    lookaty = curdistance * Sin((sigma) * (PI / 180)) * Sin((theda) * (PI / 180))

    lookatz = curdistance * Cos((sigma) * (PI / 180))

    cam.lookat.X = lookatx
    cam.lookat.Y = lookaty
    cam.lookat.Z = lookatz

End Sub
1
This is very confusing. Where in that code is your rotation supposed to happen? Why are you putting the view matrix into the projection matrix stack? Why are you multiplying view and projection in that order? What is that glOrtho doing there?derhass
In order to rotate, I change the lookat variable (x,y, and z) while keeping the position (x,y,z) the same.Eric F
But how do you change it? That's the crucial point of the whole question.derhass
@derhass I have updated my question to include my rotation class. It simply calculates where lookat should beEric F

1 Answers

1
votes

Do not use Euler angles for this as they have many issues like the one you got. Instead use cumulative transform matrices. It looks like this question is asked again and again... for some time now. So I decided to make an example how to do it with pure OpenGL 1.0 no GLM or funny stuff.

  1. Definitions

    Lets have Player control-able object called obj and camera eye. Each of them should be represented by separate 4x4 transform matrix. The OpenGL stores them as 1D arrays. For more info see

    We want to control obj in its local coordinate system independent on camera view. If you are used to have both camera and object matrices multiplied together in GL_MODELVIEW to avoid GL_PROJECTION matrix abuse then you quickly realize that this is not solvable by simply glRotate/glTranslate calls in usual manner.

    Because of that many people switch to Euler angles which can handle this easily but brings up a whole bunch of other problems (many nowadays games are still using them where they should not and there are a tons of bugs and issues because of it).

    So add this to your project:

    GLfloat mobj[16],meye[16];
    
  2. Using GL for our matrices

    This is simple just do this:

    glMatrixMode(GL_MODELVIEW); // set matrix target we want to work with does not matter really which)
    glPushMatrix(); // store original matrix so we do not mess something up
    glLoadMatrixf(mobj); // load our matrix into GL
    //here do your stuff like glRotatef(10.0,0.0,1.0,0.0);
    glGetFloatv(GL_MODELVIEW_MATRIX,mobj); // get our updated matrix from GL 
    glPopMatrix(); // restore original state
    

    With this we can use GL calls for our matrices outside the rendering loop. (for example in keyboard handler or in some timer).

  3. Rendering loop matrices

    Now if we want to render our object with our matrices then we need to set the GL matrices properly. Let assume Projection matrix is set then just Modelview is in question. The modelview matrix should be:

    GL_MODELVIEW = Inverse(meye) * mobj
    

    But OpenGL does not have any matrix inverse function. So this is the only thing we need to code. As the matrix is always 4x4 then it is not that hard.

I put all this together into this simple GL/C++/VCL example:

//---------------------------------------------------------------------------
#include <vcl.h>        // you can ignore this
#include <gl/gl.h>
#include <gl/glu.h>
#pragma hdrstop         // you can ignore this
#include "Unit1.h"      // you can ignore this
//---------------------------------------------------------------------------
// you can ignore this
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
// here some global variables
int     xs,ys;          // window resolution
HDC     hdc;            // device context
HGLRC   hrc;            // rendering context
// 4x4 transform matrices
GLfloat mobj[16];   // object transform matrix
GLfloat meye[16];   // camera transform matrix
// key codes for controling (Arrows + Space)
WORD key_left =37;
WORD key_right=39;
WORD key_up   =38;
WORD key_down =40;
WORD key_forw =32;
// key pressed state
bool _left =false;
bool _right=false;
bool _up   =false;
bool _down =false;
bool _forw =false;
bool _shift=false;
// sceene need repaint?
bool _redraw=true;
//---------------------------------------------------------------------------
// here inverse matrix computation
GLfloat matrix_subdet   (         GLfloat *a,int r,int s)
        {
        GLfloat   c,q[9];
        int     i,j,k;
        k=0;                            // q = sub matrix
        for (j=0;j<4;j++)
         if (j!=s)
          for (i=0;i<4;i++)
           if (i!=r)
                {
                q[k]=a[i+(j<<2)];
                k++;
                }
        c=0;
        c+=q[0]*q[4]*q[8];
        c+=q[1]*q[5]*q[6];
        c+=q[2]*q[3]*q[7];
        c-=q[0]*q[5]*q[7];
        c-=q[1]*q[3]*q[8];
        c-=q[2]*q[4]*q[6];
        if (int((r+s)&1)) c=-c;       // add signum
        return c;
        }
void  matrix_subdet    (GLfloat *c,GLfloat *a)
        {
        GLfloat   q[16];
        int     i,j;
        for (i=0;i<4;i++)
         for (j=0;j<4;j++)
          q[j+(i<<2)]=matrix_subdet(a,i,j);
        for (i=0;i<16;i++) c[i]=q[i];
        }
GLfloat matrix_det       (         GLfloat *a)
        {
        GLfloat c=0;
        c+=a[ 0]*matrix_subdet(a,0,0);
        c+=a[ 4]*matrix_subdet(a,0,1);
        c+=a[ 8]*matrix_subdet(a,0,2);
        c+=a[12]*matrix_subdet(a,0,3);
        return c;
        }
GLfloat matrix_det       (         GLfloat *a,GLfloat *b)
        {
        GLfloat c=0;
        c+=a[ 0]*b[ 0];
        c+=a[ 4]*b[ 1];
        c+=a[ 8]*b[ 2];
        c+=a[12]*b[ 3];
        return c;
        }
void  matrix_inv       (GLfloat *c,GLfloat *a)
        {
        GLfloat   d[16],D;
        matrix_subdet(d,a);
        D=matrix_det(a,d);
        if (D) D=1.0/D;
        for (int i=0;i<16;i++) c[i]=d[i]*D;
        }
//---------------------------------------------------------------------------
// here OpenGL stuff
//---------------------------------------------------------------------------
int TForm1::ogl_init()
    {
    // just init OpenGL
    if (ogl_inicialized) return 1;
    hdc = GetDC(Form1->Handle);             // get device context
    PIXELFORMATDESCRIPTOR pfd;
    ZeroMemory( &pfd, sizeof( pfd ) );      // set the pixel format for the DC
    pfd.nSize = sizeof( pfd );
    pfd.nVersion = 1;
    pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
    pfd.iPixelType = PFD_TYPE_RGBA;
    pfd.cColorBits = 24;
    pfd.cDepthBits = 24;
    pfd.iLayerType = PFD_MAIN_PLANE;
    SetPixelFormat(hdc,ChoosePixelFormat(hdc, &pfd),&pfd);
    hrc = wglCreateContext(hdc);            // create current rendering context
    if(hrc == NULL)
            {
            ShowMessage("Could not initialize OpenGL Rendering context !!!");
            ogl_inicialized=0;
            return 0;
            }
    if(wglMakeCurrent(hdc, hrc) == false)
            {
            ShowMessage("Could not make current OpenGL Rendering context !!!");
            wglDeleteContext(hrc);          // destroy rendering context
            ogl_inicialized=0;
            return 0;
            }
    ogl_resize();
    glEnable(GL_DEPTH_TEST);
    glDisable(GL_CULL_FACE);
    glDisable(GL_TEXTURE_2D);
    glDisable(GL_BLEND);
    glShadeModel(GL_SMOOTH);
    ogl_inicialized=1;
    return 1;
    }
//---------------------------------------------------------------------------
void TForm1::ogl_exit()
    {
    // just exit from OpneGL
    if (!ogl_inicialized) return;
    wglMakeCurrent(NULL, NULL);     // release current rendering context
    wglDeleteContext(hrc);          // destroy rendering context
    ogl_inicialized=0;
    }
//---------------------------------------------------------------------------
void TForm1::ogl_draw()
    {
    // rendering routine
    _redraw=false;

    // here the whole rendering
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);   // background color
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    GLfloat ieye[16];   // inverse camera transform matrix
    matrix_inv(ieye,meye);

    glMatrixMode(GL_MODELVIEW);
    glLoadMatrixf(ieye);
    glMultMatrixf(mobj);

    // render player controlable object
    // centered by (0,0,0)
    // +z forward, +x right, +y up
    float x=0.5,y=0.1,z=0.7;    // half sizes of object
    glColor3f(0.7,0.7,0.7);
    glBegin(GL_TRIANGLE_FAN);
    glVertex3f(0.0,0.0,+z);
    glVertex3f( -x,-y,-z);
    glVertex3f( +x,-y,-z);
    glVertex3f(0.0,+y,-z);
    glVertex3f( -x,-y,-z);
    glEnd();
    glColor3f(0.5,0.5,0.5);
    glBegin(GL_TRIANGLES);
    glVertex3f( -x,-y,-z);
    glVertex3f( +x,-y,-z);
    glVertex3f(0.0,+y,-z);
    glEnd();
    // render x,y,z axises as r,g,b lines
    glBegin(GL_LINES);
    glColor3f(1.0,0.0,0.0); glVertex3f(0.0,0.0,0.0); glVertex3f(1.0,0.0,0.0);
    glColor3f(0.0,1.0,0.0); glVertex3f(0.0,0.0,0.0); glVertex3f(0.0,1.0,0.0);
    glColor3f(0.0,0.0,1.0); glVertex3f(0.0,0.0,0.0); glVertex3f(0.0,0.0,1.0);
    glEnd();

    glFlush();
    SwapBuffers(hdc);
    }
//---------------------------------------------------------------------------
void TForm1::ogl_resize()
    {
    xs=ClientWidth;
    ys=ClientHeight;
    if (xs<=0) xs = 1;                  // Prevent a divide by zero
    if (ys<=0) ys = 1;
    if (!ogl_inicialized) return;
    glViewport(0,0,xs,ys);              // Set Viewport to window dimensions
    glMatrixMode(GL_PROJECTION);        // use projection matrix
    glLoadIdentity();                   // set it to unit matrix
    gluPerspective(30,float(xs)/float(ys),0.1,100.0); // perspective projection 30 degrees FOV and 0.1 focal length view depth 100-0.1
    glMatrixMode(GL_TEXTURE);           // use texture matrix
    glLoadIdentity();                   // set it to unit matrix
    glMatrixMode(GL_MODELVIEW);         // use modelview marix
    glLoadIdentity();                   // set it to unit matrix
    }
//---------------------------------------------------------------------------
// here window stuff
//---------------------------------------------------------------------------
// window constructor
__fastcall TForm1::TForm1(TComponent* Owner):TForm(Owner)
    {
    ogl_inicialized=0;
    hdc=NULL;
    hrc=NULL;
    ogl_init();

    // init matrices
    glMatrixMode(GL_MODELVIEW);
    // object is at (0,0,0) rotatet so Z+ is pointing to screen
    glLoadIdentity();
    glRotatef(180.0,0.0,1.0,0.0);
    glGetFloatv(GL_MODELVIEW_MATRIX,mobj);
    // camera is behind object looking at object
    glLoadIdentity();
    glTranslatef(0.0,0.0,+20.0);
    glGetFloatv(GL_MODELVIEW_MATRIX,meye);
    }
//---------------------------------------------------------------------------
// window destructor
void __fastcall TForm1::FormDestroy(TObject *Sender)
    {
    ogl_exit();
    }
//---------------------------------------------------------------------------
// common window events
//---------------------------------------------------------------------------
void __fastcall TForm1::FormResize(TObject *Sender)
    {
    ogl_resize();
    _redraw=true;
    }
//---------------------------------------------------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender)
    {
    _redraw=true;
    }
//---------------------------------------------------------------------------
void __fastcall TForm1::tim_updateTimer(TObject *Sender)
    {
    // here movement and repaint timer handler (I have 20ms interval)

    GLfloat da=5.0; // angular turn speed in [deg/timer_iteration]
    GLfloat dp=0.1; // movement speed in [world_units/timer_iteration]

    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();

    if (_shift) // if Shift pressed control camera
        {
        // copy meye to GL
        glLoadMatrixf(meye);
        // handle keyboard with GL functions
        if (_left ) { _redraw=true; glRotatef(+da,0.0,1.0,0.0); }
        if (_right) { _redraw=true; glRotatef(-da,0.0,1.0,0.0); }
        if (_up   ) { _redraw=true; glRotatef(-da,1.0,0.0,0.0); }
        if (_down ) { _redraw=true; glRotatef(+da,1.0,0.0,0.0); }
        // obtain meye from GL
        glGetFloatv(GL_MODELVIEW_MATRIX,meye);
        }
    else{ // else control object
        // copy meye to GL
        glLoadMatrixf(mobj);
        // handle keyboard with GL functions
        if (_left ) { _redraw=true; glRotatef(+da,0.0,1.0,0.0); }
        if (_right) { _redraw=true; glRotatef(-da,0.0,1.0,0.0); }
        if (_up   ) { _redraw=true; glRotatef(-da,1.0,0.0,0.0); }
        if (_down ) { _redraw=true; glRotatef(+da,1.0,0.0,0.0); }
        // obtain mobj from GL
        glGetFloatv(GL_MODELVIEW_MATRIX,mobj);
        }

    glPopMatrix();
    // handle keyboard directly
    if (_forw )
        {
        _redraw=true;
        mobj[12]+=dp*mobj[8];       // mobj[12,13,14] is object position
        mobj[13]+=dp*mobj[9];       // mobj[8,9,10] is object Z axis direction vector
        mobj[14]+=dp*mobj[10];      // if not glScale is used then it is unit in size
        }

    // render if needed
    if (_redraw) ogl_draw();
    }
//---------------------------------------------------------------------------
void __fastcall TForm1::FormMouseWheelDown(TObject *Sender, TShiftState Shift, TPoint &MousePos, bool &Handled)
    {
    // move camera matrix forward
    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
    glLoadMatrixf(meye);
    glTranslatef(0,0,+2.0);
    glGetFloatv(GL_MODELVIEW_MATRIX,meye);
    glPopMatrix();
    Handled=true;
    _redraw=true;
    }
//---------------------------------------------------------------------------
void __fastcall TForm1::FormMouseWheelUp(TObject *Sender, TShiftState Shift, TPoint &MousePos, bool &Handled)
    {
    // move camera matrix backward
    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
    glLoadMatrixf(meye);
    glTranslatef(0,0,-2.0);
    glGetFloatv(GL_MODELVIEW_MATRIX,meye);
    glPopMatrix();
    Handled=true;
    _redraw=true;
    }
//---------------------------------------------------------------------------
void __fastcall TForm1::FormKeyDown(TObject *Sender, WORD &Key,TShiftState Shift)
    {
    _shift=Shift.Contains(ssShift);
    // on key down event
    if (Key==key_left ) _left =true;
    if (Key==key_right) _right=true;
    if (Key==key_up   ) _up   =true;
    if (Key==key_down ) _down =true;
    if (Key==key_forw ) _forw =true;
    Key=0;  // key is handled
    }
//---------------------------------------------------------------------------
void __fastcall TForm1::FormKeyUp(TObject *Sender, WORD &Key, TShiftState Shift)
    {
    _shift=Shift.Contains(ssShift);
    // on key release event
    if (Key==key_left ) _left =false;
    if (Key==key_right) _right=false;
    if (Key==key_up   ) _up   =false;
    if (Key==key_down ) _down =false;
    if (Key==key_forw ) _forw =false;
    }
//---------------------------------------------------------------------------
void __fastcall TForm1::FormActivate(TObject *Sender)
    {
    _left =false; // clear key flags after focus change
    _right=false; // just to avoid constantly "pressed" keys
    _up   =false; // after window focus swaping during key press
    _down =false; // many games are ignoring this and you need to
    _forw =false; // press&release the stuck key again to stop movement ...
    }
//---------------------------------------------------------------------------

It is simple single form VCL application with single 20ms timer in it. So port the events to your environment style code. You can ignore the VCL pragmas and includes. This example is driven by arrows. If shift is pressed then the arrows are turning camera otherwise the object. Space is moving object forward.

Here compiled Win32 standalone demo:

This approach has one drawback

With cumulating the transforms you are loosing precision. To remedy that you can exploit vector multiplication (cross product). Simply count the number of operations performed on such matrix and if threshold is reached normalize the matrix and reset the counter.

By normalization I mean ensuring all the axises are unit and perpendicular to each other Leaving the direction of main axis (usually forward of view or object) as is. Cross product of 2 vectors returns perpendicular vector to each. So for example if you extract the X,Y,Z axises (locations are described in the link in #1) and Z is the main axis then:

X = Y x Z
Y = Z x X
Z = Z / |Z|
X = X / |X|
Y = Y / |Y|

Where:

// cross product: W = U x V
W.x=(U.y*V.z)-(U.z*V.y)
W.y=(U.z*V.x)-(U.x*V.z)
W.z=(U.x*V.y)-(U.y*V.x)
// dot product: a = (U.V)
a=U.x*V.x+U.y*V.y+U.z*V.z
// abs of vector a = |U|
a=sqrt((U.x*U.x)+(U.y*U.y)+(U.z*U.z))

In case your coordinate system is not derived from unit matrix then you need to negate some axis or change the order of cross product operands to ensure the direction of your axises stays as should.

For more info take a look at: