Реалистичная симуляция вращения сферы на полу

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

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

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

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

Спасибо

1 ответ

Решение

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

  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 способа, которыми я знаю, как добиться вашего вращения. Либо используйте https://en.wikipedia.org/wiki/Rodrigues%27_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 в стиле ротундус

предварительный просмотр

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

//---------------------------------------------------------------------------
#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, поэтому она не может быть идеально симметричной по пикселям...)

текстура

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

Другие вопросы по тегам