Discussion A Method of implementing 3-axis engine gimbaling

Zatnikitelman

Addon Developer
Addon Developer
Joined
Jan 13, 2008
Messages
2,302
Reaction score
6
Points
38
Location
Atlanta, GA, USA, North America
As some of you (mostly IRC loafers :p) may know I've been working on a way to implement 3-axis engine gimbaling for a dual-engine vessel. I have succeeded. With the following code, it is possible to change the exhaust vector in response to combined pitch, roll, and yaw commands. The only weakness is that it relies on the status of pre-defined thrusters so (so far) pitch, roll, yaw thruster groups have to be defined and active.
Code:
if(GetThrusterGroupLevel(THGROUP_MAIN) != 0) //prevents method from running unless main engines are firing.
    {
        pitch1 = GetThrusterGroupLevel(THGROUP_ATT_PITCHUP);
        pitch2 = GetThrusterGroupLevel(THGROUP_ATT_PITCHDOWN);
        yaw1 = GetThrusterGroupLevel(THGROUP_ATT_YAWLEFT);
        yaw2 = GetThrusterGroupLevel(THGROUP_ATT_YAWRIGHT);
        rollL = GetThrusterGroupLevel(THGROUP_ATT_BANKLEFT);
        rollR = GetThrusterGroupLevel(THGROUP_ATT_BANKRIGHT);

        //Gets the level of each thruster group and assigns the values to variables

        pitch = pitch2*(-1) + pitch1;
        lpitchangle = pitch*(PI/12); //PI/12 is the angle of deflection (15 degrees)
        rpitchangle = lpitchangle;

        //"normalizes" two groups into a single value on [-1,1] and sets the 
        //angle the engines should be at for pitch. Notice, I use two variables
        //lpitchangle and rpitchangle so the engines can be manipulated later separately.

        yaw = yaw2*(-1)+yaw1;
        lyawangle = yaw*(PI/12);
        ryawangle = lyawangle;

        //"normalizes" two groups into a single value on [-1,1] and sets the
        //angle the engines should be at for yaw. Notice, I use two variables 
        //lyawangle and ryawangle so the engines can be manipulated later separately.

        rollA = rollR+((-1)*rollL);

        //"normalizes" the two roll groups into a single value on [-1,1]. 
        //No angle is directly derived for this because the roll condition
        //affects the pitch condition of each engine separately.

        //The following cascading if statements are for calculating pitch 
        //angle when roll is combined with pitch
        if(rollA != 0) 
        {
            if(pitch > 0) //pitching up
            {
                if(rollA > 0) //roll right
                {
                    lpitchangle -= (rollA*lpitchangle);
                    //drop left engine; if the engines are pointed upwards, 
                    //the left engine drops down to allow roll right, pitch up.
                }
                if(rollA < 0) //roll left
                {
                    rpitchangle += (rollA*rpitchangle);
                    //drop right engine;
                }
            }
            else if(pitch < 0)
            {
                if(rollA > 0) //roll right
                {
                    rpitchangle -= (rollA*rpitchangle);
                    //raise right engine;
                }
                if(rollA < 0) //roll left
                {
                    lpitchangle += (rollA*lpitchangle);
                    //raise left engine;
                }
            }
            else //if there is no pitch, set the engines to diverge as much as possible.
            {
                lpitchangle = -rollA*(PI/12);
                rpitchangle = rollA*(PI/12);
            }
        }
        if(    ryawangle != 0 || rpitchangle != 0 || rollA !=0) //only changes engine direction if there is any attitude input
        {
            SetThrusterDir(th_main[0],_V(sin(lyawangle)*cos(lpitchangle),-sin(lpitchangle),cos(lyawangle)*cos(lpitchangle)));//left
            SetThrusterDir(th_main[1],_V(sin(ryawangle)*cos(rpitchangle),-sin(rpitchangle),cos(ryawangle)*cos(rpitchangle)));//right
        }//Vector multiplied by rotation matrix supplied by Mindblast
        else
        {
            SetThrusterDir(th_main[0],_V(0,0,1));
            SetThrusterDir(th_main[1],_V(0,0,1));
        }
        //if there is no attitude input, will set engines to standard 
        //configuration. Neccessary incase there was attitude input in last timestep.
    }
To use the code, it should either be placed directly in clbkPreStep, or its own method which is called at clbkPreStep.

A couple of things about this code. First, a HUGE MASSIVE THANKS to Mindblast for supplying the math to rotate a vector, this would not have been possible without that key piece of info!

Second, there is no "time delay" such that the vessel designates an orientation for the engines to go to then over the next few timesteps, the dir vector gradually approaches the commanded orientation. I tried using code similar to an animation, but am still working on it. Right now, the engines just "snap" to the final orientation when you apply attitude thrust.

Third, the cascaded if statements for finding the individual pitch conditions relative to the roll condition looks messy to me and I've been trying to somehow combine the equations so a single line can direct an engine either up or down. The problem is that the engines will be doing two different things depending on whether they're pitched up or down so where I can add the roll angle to bring an engine up, I have to subtract the roll angle to bring the engine down.

Fourth, the gimbaling doesn't like the built-in autopilots as much as I was hoping. It works well enough and kept a docked stack more stable than on thrusters alone (this code infact is from my SSM), but when the ship was free, it constantly alternated between yaw left and yaw right thrusters and the resultant gimbaling when holding prograde. This could be due to the vessel mass, Inertia Tensors etc. or because of a delay between the thrusters firing and the gimbaling activating leading to an unstable solution.

I'm posting this not only to allow other people to enable 3-axis gimbaling on their ships, but to see what improvements might can be made over this current solution.
 

Urwumpe

Not funny anymore
Addon Developer
Donator
Joined
Feb 6, 2008
Messages
37,614
Reaction score
2,335
Points
203
Location
Wolfsburg
Preferred Pronouns
Sire
Small optimization: substitute the cos(x) and sin(x) parts by c_x and s_x variables... reduces the number of calls to slow trigonometric functions at only little cost for the readability. You then only need to initialize them once.
 

mjessick

Donator
Donator
Joined
Apr 26, 2008
Messages
174
Reaction score
0
Points
0
Location
Houston
The way I have seen this programmed is with a constant "Mixer" array.
I use:
Code:
[TVC_gimbal_command_vector]^t = [mixer array] * [roll_cmd  pitch_cmd yaw_cmd]^t
TVC_gimbal_command_vector is a 4x1 column vector
The Mixer array is a 4x3 matrix
[roll_cmd pitch_cmd yaw_cmd]^T is a 3x1 column array of my autopilot axis commands.

The Mixer array "mixes" the r,p,y commands into the two angles for each engine's TVC gimballing.

My mixer array for the delta gldier looks like:
Code:
tvc_gimbal_commands_vector = mixer_matrix * auto_pilot_cmds_vector
where:
tvc_gimbal_commands_vector = 
[ left_engine_tvc_pitch,
  left_engine_tvc_yaw,
  right_engine_tvc_pitch,
  right_engine_tvc_yaw ]
 
mixer_matrix = 
[ 0.5   1.0   0.0
  0.5   0.0   1.0
 -0.5   1.0   0.0
 -0.5   0.0   1.0 ]
 
auto_pilot_cmds_vector = 
[ roll_cmd
  pitch_cmd
  yaw_cmd ]
So for a positive roll_cmd, the left engine pitches down (positive), and the right engine pitches up (negative). This provides a positive roll moment along the forward pointing axis of the ship.

Have fun coming up with your own axis definition and signs ;)

Since you are starting from 3 commands and end up with 4, there are multiple solutions: you could end up with many different mixer matrices. Since I am a guidance guy, I chose pitch and yaw as most important during the TVC ascent phase, and gave half power to the roll_cmd. This keeps the TVC angles farther away from saturation in pitch and yaw when rolling is required also.

P.S. Will someone please split the two engines apart in the delta glider for the next release? The way they were for the 2006 release, the two engine 3D models were in the same mesh group so they couldn't be gimballed (visually) in different directions. (I hacked into the mesh file to do this so I could do a "TVC Checkout", wiggling the engines in a defined pattern to verify their motions visually as part of the control algorithm testing.)

Sometimes (on small vehicles like UAVs, for example) you can do a "polarity test" where you hang the vehicle from a crane and physically rotate it, seeing whether the navigation and control hardware and algorithms all working together cause the engines to swivel in the right directions to oppose the attitude error.

---------- Post added at 10:26 PM ---------- Previous post was at 10:19 PM ----------

One more thing: to use the above, once you have the pitch and yaw (for example) gimbal angles, you would use them directly to animate the engine.

To calculate the thrust direction for the engine simulation, you calculate a two rotation rotation matrix as shown in the Orbiter manual using the gimbal angles. The appropriate row or column of the rotation matrix is the direction of the engine's thrust. You might then multiply by one more matrix to rotate the thrust direction from "engine coordinates" to body coordinates.

It may sound complicated, but all these operations are constant so you could multiply them out by hand in advance once you have them working, if desired. Working carefully step by step in this way, it is straight forward to test each stage of the algorithm.
 
Top