Реалистичная симуляция вращения сферы на полу - PullRequest
0 голосов
/ 14 октября 2018

Я пытаюсь смоделировать сферу, катящуюся по полу.Для симуляции я использую старую добрую библиотеку Papervision3D Flash AS3, но на самом деле это не имеет значения, это вопрос чистой геометрии.

Предполагая, что у меня есть объект Sphere3D, который я могу установитьсвойства вращение X, вращение Y и вращение Z, Как я могу вычислить вращение по каждой оси, где эта сфера катится по полу?

Например, давайте предположим, что сфера находится в покое.Теперь он катится на 1 метр вправо.Если я смотрю на эту сферу сверху - я хочу повернуть ее вокруг оси Z на 90 градусов.Тогда сфера должна катиться «вниз» вдоль пола, поэтому я хочу вращать ее вокруг оси X, но эта проблема заключается в том, что в то же время ось X вращалась сама по себе, когда я вращал сферу вдоль оси Z.

Как я могу решить эту проблему?

Спасибо

1 Ответ

0 голосов
/ 14 октября 2018

Если скольжения нет, то:

  1. ось вращения

    будет параллельна вашему полу и перпендикулярна вашему движению.Таким образом, вы можете использовать кросс-продукт, чтобы получить его.Пусть:


    n - вектор нормали пола
    t - направление движения параллельно полу (касательная)
    b - наша ось вращения (бинормальная)

    так что мы можем вычислить это как:

    b = cross(t,n) // cross product create perpendicular vector to both operands
    t /= |t|       // normalize to unit vector
    b /= |b|       // normalize to unit vector
    n /= |n|       // normalize to unit vector
    
  2. скорость вращения

    это может быть получено из длины дуги и скорости vel [unit/s].Поэтому, если наша сфера имеет радиус r, тогда:

    ang*r = vel*t
    ang = vel*t/r // t=1.0 sec
    omg = vel/r   // [rad/sec]
    

    , поэтому нам нужно вращать нашу сферу на omg каждую секунду.

  3. математика вращения

    углы Эйлера (ваши последовательные вращения X, Y, Z) - худшая вещь для этого, я могу думать, поскольку они приведут к особенностям и странным вещам, делающим этопростой пример ужасного кошмара для реализации.Вы видели в игре или каком-либо 3D-движке, что вдруг вы не можете смотреть, как ожидаете, или случайно вращаетесь, пока не начнете двигаться / вращаться по-другому или внезапно не повернет на 180 градусов ...?Это особенности углов Эйлера на работе без надлежащей обработки ...

    Кватернионы несколько чужды большинству людей (включая меня), поскольку они не работают так, как мы думаем.IIRC Вы можете рассматривать их как эффективный способ вычисления трехмерной матрицы вращения 3x3 с меньшим количеством гониометрических функций.Поскольку сейчас у нас сильно отличается вычислительная мощность, чем 20 лет назад, нет особого смысла выбирать их, если вы их совсем не знаете.В любом случае у них есть и другие преимущества, которые по-прежнему актуальны, например, вы можете интерполировать вращения и т. Д.

    4x4 матрицы однородного преобразования - ваш лучший выбор.Поскольку их геометрическое представление совместимо с абстрактным мышлением человека (вы можете представить, что и как это делается, поэтому вы можете создавать свои собственные матрицы вместо того, чтобы иметь их в виде множества бессмысленных чисел).

    Я настоятельно рекомендуюначать с трехмерных матриц однородного преобразования 4x4. Таким образом, весь остальной ответ будет нацелен на них.

  4. вращающийся

    Теперь есть 2 способа, которыми я знаю, как добиться вашего вращения.Либо используйте Rodrigues_rotation_formula и закодируйте его как матрицу преобразования, либо просто создайте свою собственную матрицу вращения, которая будет представлять вашу сферу, выровненную по полу.направление движения и ось вращения.

    Последнее намного проще, и мы можем сделать это напрямую, поскольку мы уже знаем 3 необходимых базисных вектора (t,b,n).Остается только позиция сферы, которая также должна быть известна.

    Итак, при запуске создайте матрицу преобразования (в предположении OpenGL):

    | tx bx nx x0 |
    | ty by ny y0 |
    | tz bz nz z0 |
    |  0  0  0  1 |
    

    Где x0,y0,z0 - начальная позицияваша сфера выровнена с вашей сеткой.Поэтому, если центральная точка вашей сетки составляет (0,0,0), тогда поместите сферу r над полом ...

    Теперь просто каждое прошедшее время dt [sec] (например, таймер) умножьте эту матрицу на приращения матрица вращения вокруг оси y (так как b - наша ось вращения) и угол omg*dt [rad].

    Нам также нужно перевести нашу сферу на t*vel*dt, поэтому просто добавьте этот вектор в матрицупозиционируйте или умножайте нашу матрицу на:

    | 1 0 0 tx*vel*dt |
    | 0 1 0 ty*vel*dt |
    | 0 0 1 tz*vel*dt |
    | 0 0 0         1 |
    

    А также визуализируйте сцену снова, используя нашу результирующую матрицу ... Этот подход хорош тем, что вы можете в любое время изменить направление движения (вы просто помните положение и изменитевнутренняя часть матрицы 3x3 вращения с новыми t,b,n векторами.

    Однако есть один недостаток, заключающийся в том, что такая накопительная матрица со временем будет снижать точность (поскольку мы выполняем умножение на числа с плавающей запятой снова и снова без сброса), поэтому матрица со временем может деформироваться.Чтобы этого избежать, достаточно время от времени пересчитывать и устанавливать часть матрицы t,b,n.Я привык делать это каждые 128 вращений с точностью до 64 бит double переменных.Это также может быть сделано автоматически (если у вас нет предварительной информации об осях). Я делаю так:

Также при использовании матриц используются разные обозначения (основной порядок строк / столбцов, порядок умножения), которые могут немного влиять на уравнения (либо обратный порядок умножения и / иливместо этого используется обратная матрица).

Теперь, если ваш 3D-движок не поддерживает матрицы (что весьма маловероятно), вам необходимо преобразовать нашу полученную матрицу обратно в углы Эйлера.Это возможно с помощью гониометрии, но для этого вам нужно знать порядок углов.

В случае скольжения вам нужно идти в обратном порядке.Поэтому сначала вычислите повороты, а затем вычислите направление перемещения от сил захвата с полом и инерцией.Это немного более сложная и чистая физика ...

[Edit1] простой стиль OpenGL / C ++ / VCL в стиле ротундуса

preview

Вот простой пример управления с использованием кумулятивной матрицы (без сохранения точности):

//---------------------------------------------------------------------------
#include <vcl.h>            // VCL stuff (ignore)
#include <math.h>           // sin,cos,M_PI
#pragma hdrstop             // VCL stuff (ignore)
#include "Unit1.h"          // VCL stuff (header of this window)
#include "gl_simple.h"      // my GL init (source included)
//---------------------------------------------------------------------------
#pragma package(smart_init) // VCL stuff (ignore)
#pragma resource "*.dfm"    // VCL stuff (ignore)
TForm1 *Form1;              // VCL stuff (this window)
//---------------------------------------------------------------------------
// vector/matrix math
//---------------------------------------------------------------------------
void  vector_mul(double *c,double *a,double *b)         // c[3] = a[3] x b[3] (cross product)
    {
    double   q[3];
    q[0]=(a[1]*b[2])-(a[2]*b[1]);
    q[1]=(a[2]*b[0])-(a[0]*b[2]);
    q[2]=(a[0]*b[1])-(a[1]*b[0]);
    for(int i=0;i<3;i++) c[i]=q[i];
    }
//---------------------------------------------------------------------------
void matrix_mul_vector(double *c,double *a,double *b)   // c[3] = a[16]*b[3] (w=1)
    {
    double q[3];
    q[0]=(a[ 0]*b[0])+(a[ 4]*b[1])+(a[ 8]*b[2])+(a[12]);
    q[1]=(a[ 1]*b[0])+(a[ 5]*b[1])+(a[ 9]*b[2])+(a[13]);
    q[2]=(a[ 2]*b[0])+(a[ 6]*b[1])+(a[10]*b[2])+(a[14]);
    for(int i=0;i<3;i++) c[i]=q[i];
    }
//---------------------------------------------------------------------------
void  matrix_inv(double *a,double *b) // a[16] = (Pseudo)Inverse(b[16])
    {
    double x,y,z;
    // transpose of rotation matrix
    a[ 0]=b[ 0];
    a[ 5]=b[ 5];
    a[10]=b[10];
    x=b[1]; a[1]=b[4]; a[4]=x;
    x=b[2]; a[2]=b[8]; a[8]=x;
    x=b[6]; a[6]=b[9]; a[9]=x;
    // copy projection part
    a[ 3]=b[ 3];
    a[ 7]=b[ 7];
    a[11]=b[11];
    a[15]=b[15];
    // convert origin: new_pos = - new_rotation_matrix * old_pos
    x=(a[ 0]*b[12])+(a[ 4]*b[13])+(a[ 8]*b[14]);
    y=(a[ 1]*b[12])+(a[ 5]*b[13])+(a[ 9]*b[14]);
    z=(a[ 2]*b[12])+(a[ 6]*b[13])+(a[10]*b[14]);
    a[12]=-x;
    a[13]=-y;
    a[14]=-z;
    }
//---------------------------------------------------------------------------
double* matrix_ld      (double *p,double a0,double a1,double a2,double a3,double a4,double a5,double a6,double a7,double a8,double a9,double a10,double a11,double a12,double a13,double a14,double a15) {                       p[0]=a0; p[1]=a1; p[2]=a2; p[3]=a3; p[4]=a4; p[5]=a5; p[6]=a6; p[7]=a7; p[8]=a8; p[9]=a9; p[10]=a10; p[11]=a11; p[12]=a12; p[13]=a13; p[14]=a14; p[15]=a15; return p; }
//---------------------------------------------------------------------------
void  matrix_mul       (double *c,double *a,double *b)  // c[16] = a[16] * b[16]
    {
    double q[16];
    q[ 0]=(a[ 0]*b[ 0])+(a[ 1]*b[ 4])+(a[ 2]*b[ 8])+(a[ 3]*b[12]);
    q[ 1]=(a[ 0]*b[ 1])+(a[ 1]*b[ 5])+(a[ 2]*b[ 9])+(a[ 3]*b[13]);
    q[ 2]=(a[ 0]*b[ 2])+(a[ 1]*b[ 6])+(a[ 2]*b[10])+(a[ 3]*b[14]);
    q[ 3]=(a[ 0]*b[ 3])+(a[ 1]*b[ 7])+(a[ 2]*b[11])+(a[ 3]*b[15]);
    q[ 4]=(a[ 4]*b[ 0])+(a[ 5]*b[ 4])+(a[ 6]*b[ 8])+(a[ 7]*b[12]);
    q[ 5]=(a[ 4]*b[ 1])+(a[ 5]*b[ 5])+(a[ 6]*b[ 9])+(a[ 7]*b[13]);
    q[ 6]=(a[ 4]*b[ 2])+(a[ 5]*b[ 6])+(a[ 6]*b[10])+(a[ 7]*b[14]);
    q[ 7]=(a[ 4]*b[ 3])+(a[ 5]*b[ 7])+(a[ 6]*b[11])+(a[ 7]*b[15]);
    q[ 8]=(a[ 8]*b[ 0])+(a[ 9]*b[ 4])+(a[10]*b[ 8])+(a[11]*b[12]);
    q[ 9]=(a[ 8]*b[ 1])+(a[ 9]*b[ 5])+(a[10]*b[ 9])+(a[11]*b[13]);
    q[10]=(a[ 8]*b[ 2])+(a[ 9]*b[ 6])+(a[10]*b[10])+(a[11]*b[14]);
    q[11]=(a[ 8]*b[ 3])+(a[ 9]*b[ 7])+(a[10]*b[11])+(a[11]*b[15]);
    q[12]=(a[12]*b[ 0])+(a[13]*b[ 4])+(a[14]*b[ 8])+(a[15]*b[12]);
    q[13]=(a[12]*b[ 1])+(a[13]*b[ 5])+(a[14]*b[ 9])+(a[15]*b[13]);
    q[14]=(a[12]*b[ 2])+(a[13]*b[ 6])+(a[14]*b[10])+(a[15]*b[14]);
    q[15]=(a[12]*b[ 3])+(a[13]*b[ 7])+(a[14]*b[11])+(a[15]*b[15]);
    for(int i=0;i<16;i++) c[i]=q[i];
    }
//---------------------------------------------------------------------------
// old style GL sphere mesh
//---------------------------------------------------------------------------
const int nb=15;            // slices
const int na=nb<<1;         // points per equator
class sphere
    {
public:
    // movement
    double r;               // sphere radius [units]
    double m[16];           // sphere direct matrix
    double vel;             // actual velocity [unit/sec] in forward direction
    void turn(double da)    // turn left/right by angle [deg]
        {
        // rotate m around global Z axis
        da*=M_PI/180.0; // [deg] -> [rad]
        double c=cos(da),s=sin(da),xyz[16];
        matrix_ld(xyz, c,-s, 0, 0,  // incremental rotation around Z
                       s, c, 0, 0,
                       0, 0, 1, 0,
                       0, 0, 0, 1);
        matrix_mul_vector(m+0,xyz,m+0); // transform all basis vectors of m from xyz [LCS] into world [GCS]
        matrix_mul_vector(m+4,xyz,m+4);
        matrix_mul_vector(m+8,xyz,m+8);
        }
    void update(double dt)  // simulate dt [sec] time is elapsed
        {
        if (fabs(vel)<1e-6) return;     // ignore stopped case
        // compute unit tangent (both vectors are unit so no normalization needed)
        double t[3]={ 0.0,0.0,1.0 };    // tangent is perpendiculr to global Z (turning axis)
        vector_mul(t,t,m+0);            // and perpendicular to local X (movement rotation axis)
        // update position
        for (int i=0;i<3;i++) m[12+i]+=vel*dt*t[i];
        // update rotation
        double da=vel*dt/r,c=cos(da),s=sin(da);
        double xyz[16];
        matrix_ld(xyz, 1, 0, 0, 0,
                       0, c,-s, 0,
                       0, s, c, 0,
                       0, 0, 0, 1);
        matrix_mul(m,xyz,m);
        }
    // mesh and rendering
    bool _init;             // has been initiated ?
    GLfloat pos[na][nb][3]; // vertex
    GLfloat nor[na][nb][3]; // normal
    GLfloat txr[na][nb][2]; // texcoord
    GLuint  txrid;          // texture id
    sphere() { _init=false; txrid=0; }
    ~sphere() { if (_init) glDeleteTextures(1,&txrid); }
    void init(GLfloat r,AnsiString texture);        // call after OpenGL is already working !!!
    void draw();
    };
void sphere::init(GLfloat _r,AnsiString texture)
    {
    GLfloat x,y,z,a,b,da,db;
    GLfloat tx0,tdx,ty0,tdy;// just correction if CLAMP_TO_EDGE is not available
    int ia,ib;
    // varables
    r=_r; vel=0.0;
    for (ia=0;ia<16;ia++ ) m[ia]=0.0;
    for (ia=0;ia<16;ia+=5) m[ia]=1.0;
    // mesh
    if (!_init) { _init=true; glGenTextures(1,&txrid); }
    // a,b to texture coordinate system
    tx0=0.0;
    ty0=0.5;
    tdx=0.5/M_PI;
    tdy=1.0/M_PI;

    // load texture to GPU memory
    if (texture!="")
        {
        Byte q;
        unsigned int *pp;
        int xs,ys,x,y,adr,*txr;
        union { unsigned int c32; Byte db[4]; } c;
        Graphics::TBitmap *bmp=new Graphics::TBitmap;   // new bmp
        bmp->LoadFromFile(texture); // load from file
        bmp->HandleType=bmDIB;      // allow direct access to pixels
        bmp->PixelFormat=pf32bit;   // set pixel to 32bit so int is the same size as pixel
        xs=bmp->Width;              // resolution should be power of 2
        ys=bmp->Height;
        txr=new int[xs*ys];
        for(adr=0,y=0;y<ys;y++)
            {
            pp=(unsigned int*)bmp->ScanLine[y];
            for(x=0;x<xs;x++,adr++)
                {
                // rgb2bgr and copy bmp -> txr[]
                c.c32=pp[x];
                q      =c.db[2];
                c.db[2]=c.db[0];
                c.db[0]=q;
                txr[adr]=c.c32;
                }
            }
        glEnable(GL_TEXTURE_2D);
        glBindTexture(GL_TEXTURE_2D,txrid);
        glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,GL_CLAMP);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,GL_CLAMP);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,GL_LINEAR);
        glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE,GL_MODULATE);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, xs, ys, 0, GL_RGBA, GL_UNSIGNED_BYTE, txr);
        glDisable(GL_TEXTURE_2D);
        delete bmp;
        delete[] txr;

        // texture coordinates by 1 pixel from each edge (GL_CLAMP_TO_EDGE)
        tx0+=1.0/GLfloat(xs);
        ty0+=1.0/GLfloat(ys);
        tdx*=GLfloat(xs-2)/GLfloat(xs);
        tdy*=GLfloat(ys-2)/GLfloat(ys);
        }
    // correct texture coordinate system (invert x)
    tx0=1.0-tx0; tdx=-tdx;

    da=(2.0*M_PI)/GLfloat(na-1);
    db=     M_PI /GLfloat(nb-1);
    for (ib=0,b=-0.5*M_PI;ib<nb;ib++,b+=db)
    for (ia=0,a= 0.0     ;ia<na;ia++,a+=da)
        {
        x=cos(b)*cos(a);
        y=cos(b)*sin(a);
        z=sin(b);
        nor[ia][ib][0]=x;
        nor[ia][ib][1]=y;
        nor[ia][ib][2]=z;
        pos[ia][ib][0]=r*x;
        pos[ia][ib][1]=r*y;
        pos[ia][ib][2]=r*z;
        txr[ia][ib][0]=tx0+(a*tdx);
        txr[ia][ib][1]=ty0+(b*tdy);
        }
    }
void sphere::draw()
    {
    if (!_init) return;
    int ia,ib0,ib1;

    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
    glMultMatrixd(m);

    glEnable(GL_TEXTURE_2D);
    glBindTexture(GL_TEXTURE_2D,txrid);
    glEnable(GL_CULL_FACE);
    glFrontFace(GL_CW);
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);

    glColor3f(1.0,1.0,1.0);
    for (ib0=0,ib1=1;ib1<nb;ib0=ib1,ib1++)
        {
        glBegin(GL_QUAD_STRIP);
        for (ia=0;ia<na;ia++)
            {
            glNormal3fv  (nor[ia][ib0]);
            glTexCoord2fv(txr[ia][ib0]);
            glVertex3fv  (pos[ia][ib0]);
            glNormal3fv  (nor[ia][ib1]);
            glTexCoord2fv(txr[ia][ib1]);
            glVertex3fv  (pos[ia][ib1]);
            }
        glEnd();
        }
    glDisable(GL_TEXTURE_2D);
    glDisable(GL_CULL_FACE);
    glDisable(GL_LIGHTING);
    glDisable(GL_LIGHT0);
/*
    // local axises
    double q=1.5*r;
    glBegin(GL_LINES);
    glColor3f(1.0,0.0,0.0); glVertex3d(0.0,0.0,0.0); glVertex3d(q,0.0,0.0);
    glColor3f(0.0,1.0,0.0); glVertex3d(0.0,0.0,0.0); glVertex3d(0.0,q,0.0);
    glColor3f(0.0,0.0,1.0); glVertex3d(0.0,0.0,0.0); glVertex3d(0.0,0.0,q);
    glEnd();
*/
    glMatrixMode(GL_MODELVIEW);
    glPopMatrix();
    }
//---------------------------------------------------------------------------
// rendring
bool _redraw=false;
double ieye[16];            // camera inverse matrix
sphere obj;
// key codes for controling (Arrows + Space)
WORD key_left =37;
WORD key_right=39;
WORD key_up   =38;
WORD key_down =40;
// key pressed state
bool _left =false;
bool _right=false;
bool _up   =false;
bool _down =false;
//---------------------------------------------------------------------------
void draw_map()
    {
    int i,j;
    double u,v,p[3],dp[3];

    // here 3D view must be already set (modelview,projection)
    glDisable(GL_CULL_FACE);

    // [draw 3D map]
    const int n=30;                 // map size
    double p0[3]={0.0,0.0,0.0};     // map start point
    double du[3]={1.0,0.0,0.0};     // map u step (size of grid = 1.0 )
    double dv[3]={0.0,1.0,0.0};     // map v step (size of grid = 1.0 )
    glColor3f(0.5,0.7,1.0);
    glBegin(GL_LINES);
    for (j=0;j<=n;j++)
        {
        for (i=0;i<3;i++) p[i]=p0[i]+(double(j)*du[i])+(double(0)*dv[i]); glVertex3dv(p);
        for (i=0;i<3;i++) p[i]=p0[i]+(double(j)*du[i])+(double(n)*dv[i]); glVertex3dv(p);
        for (i=0;i<3;i++) p[i]=p0[i]+(double(0)*du[i])+(double(j)*dv[i]); glVertex3dv(p);
        for (i=0;i<3;i++) p[i]=p0[i]+(double(n)*du[i])+(double(j)*dv[i]); glVertex3dv(p);
        }
    glEnd();
    }
//---------------------------------------------------------------------------
void gl_draw()
    {
    _redraw=false;
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glMatrixMode(GL_MODELVIEW);
    glLoadMatrixd(ieye);    // inverse camera matrix

    obj.draw();
    draw_map();

    glFlush();
    SwapBuffers(hdc);
    }
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner):TForm(Owner)
    {
    // this is called on window startup
    gl_init(Handle);                // init OpenGL 1.0
    glMatrixMode(GL_MODELVIEW);     // set camera to vew our map
    glLoadIdentity;
    glTranslatef(-15.0,-5.0,-10.5); // "centered" position above the map
    glRotatef(-60.0,1.0,0.0,0.0);   // rotate view to be more parallel to plane
    glGetDoublev(GL_MODELVIEW_MATRIX,ieye); // store result

    // ini obj
    obj.init(1.0,"ball.bmp");   // radius texture and mesh
    obj.m[12]=10.0;             // position (x,y,z)
    obj.m[13]=10.0;
    obj.m[14]=+obj.r;
    }
//---------------------------------------------------------------------------
void __fastcall TForm1::FormDestroy(TObject *Sender)
    {
    // this is called before window exits
    gl_exit();                      // exit OpenGL
    }
//---------------------------------------------------------------------------
void __fastcall TForm1::FormResize(TObject *Sender)
    {
    // this is called on each window resize (and also after startup)
    gl_resize(ClientWidth,ClientHeight);
    }
//---------------------------------------------------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender)
    {
    // this is called whnewer app needs repaint
    gl_draw();
    }
//---------------------------------------------------------------------------
void __fastcall TForm1::FormKeyDown(TObject *Sender, WORD &Key, TShiftState Shift)
    {
    // 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;
    Key=0;  // key is handled
    }
//---------------------------------------------------------------------------
void __fastcall TForm1::FormKeyUp(TObject *Sender, WORD &Key, TShiftState Shift)
    {
    // 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;
    }
//---------------------------------------------------------------------------
void __fastcall TForm1::FormMouseActivate(TObject *Sender, TMouseButton Button, TShiftState Shift, int X, int Y, int HitTest, TMouseActivate &MouseActivate)
    {
    _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
    }
//---------------------------------------------------------------------------
void __fastcall TForm1::Timer1Timer(TObject *Sender)
    {
   // here movement and repaint timer handler (I have 20ms interval)
    double dt=0.001*double(Timer1->Interval);   // timer period [sec]
    double da=90.0*dt;  // angular turn speed in [deg/sec]
    double dp=10.0*dt;  // camera movement speed in [units/sec]
    double dv=10.0*dt;  // sphere acceleration [units/sec^2]
    // control object
    if (_left ) { _redraw=true; obj.turn(-da); }
    if (_right) { _redraw=true; obj.turn(+da); }
    if (_up   ) { _redraw=true; obj.vel+=dv; }
    if (_down ) { _redraw=true; obj.vel-=dv; }
    // simulate the ball movement
    obj.update(dt); _redraw=true;
    // render if needed
    if (_redraw) gl_draw();
    }
//---------------------------------------------------------------------------

Это пустое приложение VCL для одной формы с одним таймером 20 мс.Для того чтобы портировать в вашу среду, просто игнорируйте материал VCL, имитируйте соответствующие события приложения и рендеринга порта для ваших компонентов / style / api.Единственная важная вещь - это просто класс sphere, помеченный как // movement, и событие таймера Timer1Timer(TObject *Sender).Все остальное - просто рендеринг и обработка клавиатуры ... Я подозреваю, что вы уже справились с этим самостоятельно ...

Предварительный просмотр показывает движение, пока я управляю мячом со стрелками:

up/down - accelerate/decelerate
left/right - turn left/right in respect to forward direction around normal to surface

Здесь я использовал текстуру (нарисованную в mspaint вручную, поэтому она не может быть идеально симметричной по пикселям ...)

texture

gl_simple.h изМой может быть найден здесь:

...