地板上球体旋转的真实模拟 [英] Realistic simulation of sphere rotation on a floor

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

问题描述

我正在尝试模拟在地板上滚动的球体.对于模拟,我使用的是 Flash AS3 的老式 Papervision3D 库,但实际上没有关系,这是一个纯几何问题.

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

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

我该如何解决这个问题?

谢谢

解决方案

如果没有滑动则:

  1. 旋转轴

    将平行于您的地板并垂直于您的运动.所以你可以利用交叉产品来获得它.让:


    n - 楼层法向量
    t - 平行于地面的运动方向(切线)
    b - 我们的旋转轴(副法线)

    所以我们可以将其计算为:

    b = cross(t,n)//叉积创建两个操作数的垂直向量t/= |t|//归一化为单位向量b/= |b|//归一化为单位向量n/= |n|//归一化为单位向量

  2. 旋转速度

    这可以从弧长和速度推导出vel [unit/s].所以如果我们的球体的半径为 r 那么:

    ang*r = vel*tang = vel*t/r//t=1.0 秒omg = vel/r//[弧度/秒]

    所以我们需要每秒将球体旋转 omg.

  3. 旋转数学

    欧拉角(你的顺序旋转 X、Y、Z)是我能想到的最糟糕的事情,因为它们会导致奇点和奇怪的东西,使这个简单的例子成为可怕的噩梦.你有没有在游戏或任何 3D 引擎中看到突然你不能看起来像你期望的那样,或者随机旋转直到你移动/旋转不同或突然旋转 180 度......?这是欧拉角奇点在没有适当处理的情况下工作...

    四元数对大多数人(包括我)来说有些陌生,因为它们不像我们想象的那样工作.IIRC 您可以将它们视为计算 3x3 3D 旋转矩阵的有效方法,所需的测角函数较少.由于我们现在的计算能力与 20 年前大不相同,如果您根本不了解它们,那么选择它们也没有多大意义.无论如何,它们还有另一个仍然相关的优点,例如您可以在旋转等之间进行插值.

    这里使用累积矩阵的简单控制示例(不保留精度):

    //---------------------------------------------------------------------#include //VCL的东西(忽略)#include <math.h>//sin,cos,M_PI#pragma hdrstop//VCL 内容(忽略)#include "Unit1.h"//VCL 内容(此窗口的标题)#include "gl_simple.h"//我的 GL 初始化(包括源代码)//---------------------------------------------------------------------------#pragma package(smart_init)//VCL 的东西(忽略)#pragma resource "*.dfm"//VCL 内容(忽略)TForm1 *Form1;//VCL 的东西(这个窗口)//---------------------------------------------------------------------------//向量/矩阵数学//---------------------------------------------------------------------------void vector_mul(double *c,double *a,double *b)//c[3] = a[3] x b[3](叉积){双 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){双 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]){双 x,y,z;//旋转矩阵的转置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;//复制投影部分a[3]=b[3];a[7]=b[7];a[11]=b[11];a[15]=b[15];//转换原点:new_pos = - new_rotation_matrix * old_posx=(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,双 a14,双 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;返回 p;}//---------------------------------------------------------------------------void matrix_mul (double *c,double *a,double *b)//c[16] = a[16] * b[16]{双 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];}//---------------------------------------------------------------------------//旧式 GL 球体网格//---------------------------------------------------------------------------const int nb=15;//切片const int na=nb<<1;//每个赤道点阶级领域{民众://移动双 r;//球体半径 [单位]双 m[16];//球面直接矩阵双绒;//向前的实际速度[单位/秒]void turn(double da)//向左/向右转角度 [度]{//围绕全局 Z 轴旋转 mda*=M_PI/180.0;//[度] ->[弧度]双 c=cos(da),s=sin(da),xyz[16];matrix_ld(xyz, c,-s, 0, 0,//围绕 Z 的增量旋转s, c, 0, 0,0, 0, 1, 0,0, 0, 0, 1);matrix_mul_vector(m+0,xyz,m+0);//将 m 的所有基向量从 xyz [LCS] 转换为世界 [GCS]matrix_mul_vector(m+4,xyz,m+4);matrix_mul_vector(m+8,xyz,m+8);}void update(double dt)//模拟 dt [sec] 时间已过{如果 (fabs(vel)<1e-6) 返回;//忽略停止的情况//计算单位切线(两个向量都是单位所以不需要归一化)双 t[3]={0.0,0.0,1.0};//切线垂直于全局 Z(旋转轴)vector_mul(t,t,m+0);//并垂直于局部 X(运动旋转轴)//更新位置对于 (int i=0;i<3;i++) m[12+i]+=vel*dt*t[i];//更新旋转双 da=vel*dt/r,c=cos(da),s=sin(da);双 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);}//网格和渲染布尔_init;//已启动?GLfloat pos[na][nb][3];//顶点GLfloatnor[na][nb][3];//普通的GLfloat txr[na][nb][2];//文本坐标GLuint txrid;//纹理标识球体(){_init=false;txrid=0;}~sphere() { if (_init) glDeleteTextures(1,&txrid);}无效初始化(GLfloat r,AnsiString 纹理);//在 OpenGL 已经工作之后调用!!!无效绘制();};void sphere::init(GLfloat _r,AnsiString 纹理){GLfloat x,y,z,a,b,da,db;GLfloat tx0,tdx,ty0,tdy;//CLAMP_TO_EDGE 不可用时的修正内部, ib;//变量r=_r;vel=0.0;对于 (ia=0;ia<16;ia++) m[ia]=0.0;对于 (ia=0;ia<16;ia+=5) m[ia]=1.0;//网如果(!_init){_init=true;glGenTextures(1,&txrid);}//a,b 到纹理坐标系tx0=0.0;ty0=0.5;tdx=0.5/M_PI;tdy=1.0/M_PI;//将纹理加载到 GPU 内存如果(纹理!="){字节 q;无符号整数 *pp;int xs,ys,x,y,adr,*txr;union { unsigned int c32;字节 db[4];} C;图形::TBitmap *bmp=新图形::TBitmap;//新的 bmpbmp->LoadFromFile(texture);//从文件加载bmp->HandleType=bmDIB;//允许直接访问像素bmp->PixelFormat=pf32bit;//将像素设置为 32 位,因此 int 与像素大小相同xs=bmp->宽度;//分辨率应该是 2 的幂ys=bmp->高度;txr=new int[xs*ys];for(adr=0,y=0;yScanLine[y];for(x=0;xtxr[]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);删除 bmp;删除[] txr;//纹理坐标距每条边 1 个像素 (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);}//正确的纹理坐标系(反转 x)tx0=1.0-tx0;tdx=-tdx;da=(2.0*M_PI)/GLfloat(na-1);db= M_PI/GLfloat(nb-1);对于 (ib=0,b=-0.5*M_PI;ibInterval);//定时器周期 [秒]双 da=90.0*dt;//角转向速度 [度/秒]双 dp=10.0*dt;//相机移动速度 [单位/秒]双dv=10.0*dt;//球体加速度 [单位/秒^2]//控制对象如果(_left){_redraw=true;obj.turn(-da);}如果 (_right) { _redraw=true;obj.turn(+da);}如果 (_up) { _redraw=true;obj.vel+=dv;}如果(_down){_redraw=true;obj.vel-=dv;}//模拟球的运动obj.update(dt);_重绘=真;//需要时渲染如果(_redraw)gl_draw();}//---------------------------------------------------------------------------

    它是一个空的单一形式的 VCL 应用程序,上面有一个 20 毫秒的计时器.为了移植到您的环境,只需忽略 VCL 内容,模拟应用程序的相关事件并将渲染移植到您的组件/样式/api.唯一重要的就是标记为 //motionsphere 类和计时器事件 Timer1Timer(TObject *Sender).剩下的只是渲染和键盘处理......我怀疑你已经自己处理了......

    预览显示我用箭头控制球时的运动:

    up/down - 加速/减速左/右 - 相对于表面法线的向前方向左/右转

    这里我使用的纹理(手工在 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:

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

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆