Rotating an Object Around an Axis

2019-09-07 04:09发布

问题:

I have a circular shape object, which I want to rotate like a fan along it's own axis.

I can change the rotation in any direction i.e. dx, dy, dz using my transformation matrix.

The following it's the code:

 Matrix4f matrix = new Matrix4f();
 matrix.setIdentity();
 Matrix4f.translate(translation, matrix, matrix);
 Matrix4f.rotate((float) Math.toRadians(rx), new Vector3f(1,0,0), matrix, matrix);
 Matrix4f.rotate((float) Math.toRadians(ry), new Vector3f(0,1,0), matrix, matrix);
 Matrix4f.rotate((float) Math.toRadians(rz), new Vector3f(0,0,1), matrix, matrix);
 Matrix4f.scale(new Vector3f(scale,scale,scale), matrix, matrix);

My vertex code:

 vec4 worldPosition = transformationMatrix * vec4(position,1.0);
 vec4 positionRelativeToCam = viewMatrix*worldPosition;
 gl_Position = projectionMatrix *positionRelativeToCam;


Main Game Loop:



  Object.increaseRotation(dxf,dyf,dzf);

But, it's not rotating along it's own axis. What am I missing here? I want something like this. Please Help

回答1:

You should Get rid of Euler angles for this.

  1. Object/mesh geometry

    You need to be aware of how your object is oriented in its local space. For example let assume this:

    So in this case the main rotation is around axis z. If your mesh is defined so the rotation axis is not aligned to any of the axises (x,y or z) or the center point is not (0,0,0) than that will cause you problems. The remedy is either change your mesh geometry or create a special constant transform matrix M0 that will transform all vertexes from mesh LCS (local coordinate system) to a different one that is axis aligned and center of rotation has zero in the axis which is also the axis of rotation.

    In the latter case any operation on object matrix M would be done like this:

    M'=M.M0.operation.Inverse(M0)
    

    or in reverse or in inverse (depends on your matrix/vertex multiplication and row/column order conventions). If you got your mesh already centered and axis aligned then do just this instead:

    M'=M.operation
    

    The operation is transform matrix of the change increment (for example rotation matrix). The M is the object current transform matrix from #2 and M' is its new version after applying operation.

  2. Object transform matrix

    You need single Transform matrix for each object you got. This will hold the position and orientation of your object LCS so it can be converted to world/scene GCS (global coordinate system) or its parent object LCS

  3. rotating your object around its local axis of rotation

    As in the Understanding 4x4 homogenous transform matrices is mentioned for standard OpenGL matrix convetions you need to do this:

    M'=M*rotation_matrix
    

    Where M is current object transform matrix and M' is the new version of it after rotation. This is the thing you got different. You are using Euler angles rx,ry,rz instead of accumulating the rotations incrementally. You can not do this with Euler angles in any sane and robust way! Even if many modern games and apps are still trying hard to do it (and failing for years).

So what to do to get rid of Euler angles:

  1. You must have persistent/global/static matrix M per object

    instead of local instance per render so you need to init it just once instead of clearing it on per frame basis.

  2. On animation update apply operation you need

    so:

    M*=rotation_around_z(angspeed*dt);
    

    Where angspeed is in [rad/second] or [deg/second] of your fan speed and dt is time elapsed in [seconds]. For example if you do this in timer then dt is the timer interval. For variable times you can measure the time elapsed (it is platform dependent I usually use PerformanceTimers or RDTSC).

    You can stack more operations on top of itself (for example your fan can also turning back and forward around y axis to cover more area.

    For object direct control (by keyboard,mouse or joystick) just add things like:

    if (keys.get( 38)) { redraw=true; M*=translate_z(-pos_speed*dt); }
    if (keys.get( 40)) { redraw=true; M*=translate_z(+pos_speed*dt); }
    if (keys.get( 37)) { redraw=true; M*=rotation_around_y(-turn_speed*dt); } 
    if (keys.get( 39)) { redraw=true; M*=rotation_around_y(+turn_speed*dt); }
    

    Where keys is my key map holding on/off state for every key in the keyboard (so I can use more keys at once). This code just control object with arrows. For more info on the subject see related QA:

    Computer Graphics: Moving in the world

  3. Preserve accuracy

    With incremental changes there is a risc of loosing precision due to floating point errors. So add a counter to your matrix class which counts how many times it has been changed (incremental operation applied) and if some constant count hit (for example 128 operations) Normalize your matrix.

    To do that you need to ensure orthogonormality of your matrix. So eaxh axis vector X,Y,Z must be perpendicular to the other two and its size has to be unit. I do it like this:

    1. Choose main axis which will have unchanged direction. I am choosing Z axis as that is usually my main axis in my meshes (viewing direction, rotation axis etc). so just make this vector unit Z = Z/|Z|
    2. exploit cross product to compute the other two axises so X = (+/-) Z x Y and Y = (+/-) Z x X and also normalize them too X = X/|X| and Y = Y/|Y|. The (+/-) is there because I do not know your coordinate system conventions and the cross product can produce opposite vector to your original direction so if the direction is opposite change the multiplication order or negate the result (this is done while coding time not in runtime!).

    Here example in C++ how my orthonormal normalization is done:

    void reper::orto(int test)
            {
            double   x[3],y[3],z[3];
            if ((cnt>=_reper_max_cnt)||(test)) // here cnt is the operations counter and test force normalization regardless of it
                    {
                    use_rep();              // you can ignore this 
                    _rep=1; _inv=0;         // you can ignore this
                    axisx_get(x);
                    axisy_get(y);
                    axisz_get(z);
                    vector_one(z,z);
                    vector_mul(x,y,z);      // x is perpendicular to y,z
                    vector_one(x,x);
                    vector_mul(y,z,x);      // y is perpendicular to z,x
                    vector_one(y,y);
                    axisx_set(x);
                    axisy_set(y);
                    axisz_set(z);
                    cnt=0;
                    }
            }
    

    Where axis?_get/set(a) just get/set a as axis from/to your matrix. The vector_one(a,b) returns a = b/|b| and vector_mul(a,b,c) return a = b x c