本文介绍了地板上球体旋转的逼真模拟的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试模拟在地板上滚动的球体.对于模拟,我使用的是Flash AS3的古老的Papervision3D库,但实际上并不重要,这是一个纯粹的几何问题.

假设我有一个Sphere3D对象,可以为其设置rotationX,rotationY和rotationZ属性,那么如何计算该球体在地板上滚动的每个轴的旋转?

例如,假设球体处于静止状态.现在它向右滚动1米.如果我从顶部看这个球体-我想将其绕Z轴旋转90度.然后,球体应该沿着地板向下"滚动,因此我想绕X轴旋转球,但是这个问题是,与此同时,当我沿Z轴旋转球体时,X轴也会自转. >

我该如何解决这个问题?

谢谢

解决方案

如果没有滑动,则:

  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 3D旋转矩阵的有效方法,所需的测角功能更少.由于我们现在的计算能力与20年前大不相同,因此即使您根本不了解它们,选择它们也没有多大意义.无论如何,它们还有另一个仍然有用的优点,例如您可以在旋转之间进行插补等.

    4x4均匀变换矩阵 是您的最佳选择.由于它们的几何表示形式与人类的抽象思维兼容(您可以想象它的完成方式和方式,因此可以构造自己的矩阵,而不必将它们当作一堆毫无意义的数字).

    我强烈建议从3D 4x4齐次变换矩阵开始.因此,所有其他答案将针对他们.

  4. 旋转

    现在,我知道有两种方法可以实现轮换.可以使用 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 |
    

    并使用我们得到的矩阵再次渲染场景...这种方法非常有用,因为您可以随时更改运动方向(您只需记住位置并使用新的t,b,n向量更改矩阵的内部3x3旋转部分即可.

    但是,这样的累积矩阵存在一个缺点,即随着时间的流逝会降低精度(因为我们通过在其上反复进行浮点数的乘法而不重置)来使矩阵随时间变形.为了避免这种情况,足以重新计算并不时设置矩阵的t,b,n部分.我习惯于在64位double变量精度上每128圈进行一次旋转.它也可以自动完成(当您没有有关轴的先验信息时),我这样做是这样的:

使用矩阵也有不同的符号(行/列的主要顺序,乘法顺序),这可能会稍微影响方程(乘法的逆顺序和/或使用逆矩阵).

现在,如果您的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应用,上面有一个20ms计时器.为了移植到您的环境,只需忽略VCL内容,模仿应用程序的相关事件,然后将渲染移植到您的组件/样式/api.唯一重要的东西就是标记为// movementsphere类和计时器事件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可以在这里找到:

I'm trying to simulate a sphere rolling on a floor.For the simulation I'm using the good-old Papervision3D library of Flash AS3, but it actually doesn't matter, this is a pure geometry question.

Assuming that I have a Sphere3D object to which I can set the rotationX, rotationY and rotationZ properties, How can I calculate the rotation in every axis where this sphere is rolling on the floor?

For instance, Let's assume the sphere is in rest. Now it rolls 1 meter to the right. If I'm looking at this sphere from the top - I'll want to rotate it around the Z axis, 90 degrees.Then the sphere should roll "downwards" along the floor, so I'll want to rotate it around the X axis, but this problem is that in the meantime the X axis rotated itself when I rotated the sphere along the Z axis.

How can I solve this issue?

Thanks

解决方案

If there is no sliding then:

  1. rotation axis

    will be parallel to your floor and perpendicular to your movement. So you can exploit cross product to get it. Let:


    n - floor normal vector
    t - movement direction parallel with floor (tangent)
    b - our rotation axis (binormal)

    so we can compute it as:

    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. rotation speed

    this can be derived from arclength and speed vel [unit/s]. So if our sphere is of radius r then:

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

    so we need to rotate our sphere by omg each second.

  3. rotation math

    Euler angles (your sequenced rotations X,Y,Z) are the worst thing for this I can think of as they will lead to singularities and weird stuff making this simple example horrible nightmare to implement. have you seen in a game or any 3D engine that suddenly you can not look as you expect, or randomly spin until you move/rotate differently or suddenly rotate by 180deg ... ? That are Euler angles singularities at work without proper handling...

    Quaternions are somewhat alien to most people (me included) as they do not work like we think. IIRC You can look at them as efficient way of computing 3x3 3D rotation matrix with less goniometric functions needed. As we now have much different computational power than 20 years ago theres not much point choosing them if you do not know them at all. Anyway they have also another advantages which are still relevant like you can interpolate between rotations etc.

    4x4 homogenuous transform matrices are your best choice. As their geometric representation is compatible with human abstract thinking (you can imagine what and how it is done hence you can construct your own matrices instead of having them as bunch of meaningless numbers).

    I strongly recommend to start with 3D 4x4 homogenuous transform matrices. So all the rest of this answer will be aimed to them.

  4. rotating

    Now There are 2 ways I know of how to achieve your rotation. Either use Rodrigues_rotation_formula and encode it as transform matrix or simply construct your own rotation matrix that will represent your sphere aligned to floor. direction of movement and rotation axis.

    The latter is much much simpler and we can do it directly as we already know the 3 basis vectors needed (t,b,n). What is left is only the sphere position which should be also known.

    So at start create a transform matrix (assuming OpenGL notation):

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

    Where x0,y0,z0 is start position of your sphere aligned with your mesh. So if center point of your mesh is (0,0,0) then place your sphere r above the floor...

    Now just each elapsed time dt [sec] (like timer) multiply this matrix by incremental rotation matrix around y axis (as b is our rotation axis) and angle omg*dt [rad].

    We also need to translate our sphere by t*vel*dt so simply add this vector to matrix position or multiply our matrix with:

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

    And also render the scene again using our resulting matrix...This approach is nice as you can anytime change the direction of movement (you just remember the position and change the inner 3x3 rotation part of the matrix with new t,b,n vectors.

    However there is one disadvantage that such cumulative matrix will degrade the accuracy over time (as we are performing multiplication by floating numbers over and over on it without reset) so the matrix can deform over time. To avoid this is enough to recompute and set the t,b,n part of the matrix from time to time. I am used to do it each 128 rotations on 64bit double variables precision. It can be done also automatically (when you have no prior info about the axises) I am doing like this:

Also using matrices have different notations (row/column major order, multiplication order) which can affect the equations a bit (either reverse order of multiplication and/or using inverse matrices instead).

Now in case your 3D engine does not support matrices (which is highly unlikely) you would need to convert our resulting matrix back into Euler angles. That is doable by goniometrics but for that you would need to know the order of the angles.

In case of Sliding you need to go in reverse order. So first compute the rotations and then compute the direction of translation from the grip forces with floor and inertia. Which is a bit more complex and pure physics ...

[Edit1] rotundus style simple OpenGL/C++/VCL example

Here simple control example using cumulative matrix (without the accuracy preservation):

//---------------------------------------------------------------------------
#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();
    }
//---------------------------------------------------------------------------

Its an empty single form VCL app with single 20ms timer on it. In order to port to your environment just ignore the VCL stuff, mimic the relevant events of the app and port rendering to your components/style/api. The only important stuff is just the sphere class marked as // movement and the timer event Timer1Timer(TObject *Sender). All the rest is just rendering and keyboard handling ... Which I susspect you already got handled on your own ...

The preview shows movement while I control the ball with arrows:

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

Here texture I used (drawed in mspaint by hand so it might not be pixel perfect symmetrical...)

The gl_simple.h of mine can be found in here:

这篇关于地板上球体旋转的逼真模拟的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-15 02:53