我正在尝试模拟在地板上滚动的球体。 为了进行仿真,我使用的是Flash AS3的古老Papervision3D库,但这并不重要,这是一个纯几何问题。
假设我有一个Sphere3D对象,可以为其设置rotationX,rotationY和rotationZ属性,那么如何计算该球体在地板上滚动的每个轴的旋转?
例如,假设球体处于静止状态。现在它向右滚动1米。如果我从顶部看这个球体-我想将其绕Z轴旋转90度。 然后,球体应该沿着地板“向下”滚动,因此我想绕X轴旋转球,但是这个问题是,与此同时,当我沿Z轴旋转球体时,X轴也会旋转。 >
我该如何解决这个问题?
谢谢
答案 0 :(得分:2)
如果没有滑动,则:
旋转轴
将平行于您的地板并垂直于您的运动。因此,您可以利用跨产品来获取它。让:
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
转速
这可以从弧长和速度vel [unit/s]
中得出。因此,如果我们的球体的半径为r
,则:
ang*r = vel*t
ang = vel*t/r // t=1.0 sec
omg = vel/r // [rad/sec]
所以我们需要每秒将球体旋转omg
。
旋转数学
欧拉角(您的顺序旋转X,Y,Z)是最糟糕的事情,因为它们会导致奇异和怪异的东西,从而使这个简单的示例变得可怕。您在游戏或任何3D引擎中看到过突然无法按照您预期的样子观看,还是随机旋转直到您以不同方式移动/旋转或突然旋转180度...吗?那是欧拉角的奇点在工作中没有适当的处理...
四元数对于大多数人(包括我在内)有些陌生,因为它们的工作方式不像我们想象的那样。 IIRC您可以将它们视为计算3x3 3D旋转矩阵的有效方法,所需的测角功能更少。由于我们现在的计算能力与20年前大不相同,因此即使您根本不了解它们,选择它们也没有多大意义。无论如何,它们还有另一个仍然有用的优点,例如您可以在旋转之间进行插补等。
4x4 homogenuous transform matrices是您的最佳选择。由于它们的几何表示形式与人类的抽象思维兼容(您可以想象它的完成方式和方式,因此可以构造自己的矩阵,而不必将它们当作一堆毫无意义的数字)。
我强烈建议从3D 4x4齐次变换矩阵开始。因此,所有其他答案将针对他们。
旋转
现在,我知道有两种方法可以实现轮换。您可以使用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示例
以下是使用累积矩阵的简单控制示例(不保留精度):
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中,因此它可能不是像素完美对称的...)
我的Timer1Timer(TObject *Sender)
可以在这里找到: