地板上球体旋转的真实模拟

时间:2018-10-13 23:43:26

标签: 3d geometry physics

我正在尝试模拟在地板上滚动的球体。 为了进行仿真,我使用的是Flash AS3的古老Papervision3D库,但这并不重要,这是一个纯几何问题。

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

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

我该如何解决这个问题?

谢谢

1 个答案:

答案 0 :(得分:2)

如果没有滑动,则:

  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 homogenuous transform matrices是您的最佳选择。由于它们的几何表示形式与人类的抽象思维兼容(您可以想象它的完成方式和方式,因此可以构造自己的矩阵,而不必将它们当作一堆毫无意义的数字)。

    我强烈建议从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是我们的旋转轴)的增量rotation matrix和角度{{ 1}}。

    我们还需要通过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部分。我习惯在64位t,b,n变量精度上每128圈执行一次。也可以自动完成(当您没有有关轴的先验信息时)我正在这样做:

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

现在,如果您的3D引擎不支持矩阵(这种可能性很小),则需要将我们得到的矩阵转换回欧拉角。用测角学可以做到这一点,但是为此您需要知道角度的顺序。

如果发生滑动,则需要以相反的顺序进行。因此,首先计算旋转,然后根据具有地面和惯性的抓地力计算平移方向。这是更加复杂和纯粹的物理...

[Edit1]圆形建筑简单的OpenGL / C ++ / VCL示例

preview

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

double

它是一个空的单一表单VCL应用,上面有一个20ms计时器。为了移植到您的环境,只需忽略VCL内容,模仿应用程序的相关事件,然后将渲染移植到组件/样式/ api。唯一重要的东西只是标记为//--------------------------------------------------------------------------- #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(); } //--------------------------------------------------------------------------- 的{​​{1}}类和计时器事件sphere。其余的只是渲染和键盘处理...我怀疑您已经自己处理了...

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

// movement

这里使用的纹理(手工绘制在mspaint中,因此它可能不是像素完美对称的...)

texture

我的Timer1Timer(TObject *Sender)可以在这里找到: