# How to Add a new Attitude Parameter

*by Dunn Idle ** Thanks to Linda Jun for the extra guidance.*

##### Introduction

The following procedure was developed for adding a new type of attitude parameter to GMAT. There may be steps still to be documented if a user needs to propagate a variable as part of an entity's internal state, but these instructions will cover the specifics of adding Modified Rodrigues Parameters as part of the Attitude Tab of the Spacecraft GUI Dialog Box. This involves not only converting between parameter types, but getting wxWidgets to show the new parameters as part of the GUI. We will also look at adding these parameters to the Parameter classes at a top level in GMAT so they can be plotted in the XYPlot. We will attempt to provide steps in an order that as you accomplish them, you can recompile and run the code without it crashing. It may not show the new parameters yet, or use them, but hopefully each step will be compatible with continuing operations of GMAT. That way you don't have to accomplish all these steps completely before continuing to use your current solution. Hopefully the entire set of steps below will give the GMAT programmer a feel for what is required to add a new parameter to GMAT. There are many types of parameters in addition to "Attitude" parameters, but this is a real world example of how MRPs were added and includes every step that was needed. Hopefully, the next person to add a parameter will find a large percentage of overlap between what they need to do and what is shown here. This is not as detailed as adding a new "resource" but should serve as a good introduction as to how GMAT is designed with user extensibility in mind.

##### Step 1:

In the directory **src\base\attitude**, open the file **Attitude.hpp**.

##### Step 2:

Make the following additions to **Attitude.hpp** –

- In the declaration of the namespace
**GmatAttitude**, add**MODIFIED_RODRIGUES_PARAMETERS_TYPE**to the**AttitudeStateType**enumerations list. This will be the fourth type, right under**EULER_ANGLES_AND_SEQUENCE_TYPE**. - In the public section of the declaration of the
**GMAT_API Attitude**class, under the heading**BEGIN static methods for conversion**, right after the last**ToQuaternion**method is declared, add the following two method declarations.

*static Rvector ToQuaternion(const Rvector3 &MRPs);static Rvector3 ToMRPs(const Rvector &quat1);*

- In the protected section of the same
**GMAT_API Attitude**class, in the enum declarations of all the attitude enumerations, after the final direction cosine matrix entry, called**DCM_33**, add the following three enumerations.

*MRP_1,MRP_2,MRP_3,*

- In the same protected section, in the enum
**OtherReps**declaration of all the "other" possible enumerations, after the**QUATERNION**entry, add the following.

*MRPS,*

- Further down in the same protected section, right after the "last computed quaternion" which is declared as
**Rvector quaternion;**, add the following.

*Rvector3 mrps;*

- In the declaration of the
**ComputeCosineMatrixAndAngularVelocity**method, in the private section, under**ValidateQuaternion**, add the following method declaration.

*bool ValidateMRPs(const Rvector &mrps);*

##### Step 3:

Re build GMAT with the new version of **Attitude.hpp**.

##### Step 4:

Next let's modify **Attitude.cpp** found in the same **src\base\attitude** directory.

- In the definition of
**Attitude::PARAMETER_TEXT**, add the following under the final direction cosine**"DCM33".**

*"MRP1","MRP2","MRP3",*

- Just below that, in the definition of
**Attitude::PARAMETER_TYPE**, in the same location as the variables above (just count positions), add the following.

*Gmat::REAL_TYPE, Gmat::REAL_TYPE, Gmat::REAL_TYPE,*

- Just below that, in the definition of
**Attitude::OTHER_REP_TEXT**, just under**"Quaternion"**, add the following.

*"MRPs",*

- Just below that, in the definition of
**Attitude::OTHER_REP_TYPE**, in the same location as**"MRPs"**above, which is third from the bottom, add the following.

*Gmat::RVECTOR_TYPE,*

- Next we write and place the methods for converting from quaternions to MRPs, and from MRPs to quaternions. Since this is a procedure for adding a new parameter, we won't worry about the actual algorithms. They are simple enough and we can get them from the Attitude section of the GMAT Math Specification. Just below the final
**ToQuaternion**method, insert the following two new methods.

*Rvector Attitude::ToQuaternion(const Rvector3 &MRPs)Rvector3 Attitude::ToMRPs(const Rvector &quat1)*

- In the copy constructor
**Attitude::Attitude(const Attitude& att) :**under the quaternion entry, add the following.

*mrps (att.mrps),*

- In the assignment operator
**Attitude& Attitude::operator=(const Attitude& att)**under the quaternion entry, add the following.

*mrps = att.mrps;*

- In the
**bool Attitude::Initialize()**method, we are computing the cosine matrix based on the input attitude type. There is a switch statement that is based on input type. This statement is**switch (inputAttitudeType)**. We now have a new type,**MRPs**, that we need to account for. So we add the new**MRP**case, right under the**DIRECTION_COSINE_MATRIX_TYPE**case. We add the following code.

*case GmatAttitude::MODIFIED_RODRIGUES_PARAMETERS_TYPE: ValidateMRPs(mrps); quaternion = Attitude::ToQuaternion(mrps); cosMat = Attitude::ToCosineMatrix(quaternion); break;*

- Note that in the above step, we still have to write the
**ValidateMRPs(mrps)**method and insert it somewhere below. The**ToQuaternion**method that takes**mrps**as an input is something we have already written and inserted above. The method named**ToCosineMatrix**already exists. - In the
**bool Attitude::IsParameterReadOnly(const Integer id) const**method, find the logic statement that checks if**attitudeDisplayType**is**"Quaternion"**. Where it checks if the**id**is equal to**EULER_ANGLE_1**through**DCM_33**, find an appropriate place to check if**id**is equal to**MRPs**. The result should look like this.

*if ((id == EULER_ANGLE_1) || (id == EULER_ANGLE_2) || (id == EULER_ANGLE_3) || (id == MRP_1) || (id == MRP_2) || (id == MRP_3) || (id == DCM_11) || (id == DCM_12) || (id == DCM_13) || (id == DCM_21) || (id == DCM_22) || (id == DCM_23) || (id == DCM_31) || (id == DCM_32) || (id == DCM_33)) return true;*

- In the same method, find the logic statement that checks if
**attitudeDisplayType**is**"DirectionCosineMatrix"**. Where it checks if**id**is equal to**Q_1**through**EULER_ANGLE_3**, find an appropriate place to check if**id**is equal to**MRPs**. The result should look like this.

*if ((id == Q_1) || (id == Q_2) || (id == Q_3) || (id == Q_4) || (id == MRP_1) || (id == MRP_2) || (id == MRP_3) || (id == EULER_ANGLE_1) || (id == EULER_ANGLE_2) || (id == EULER_ANGLE_3)) return true;*

- Now it's time to write an entire section for what happens if
**attitudeDisplayType**is**"MRPs"**. Here is what we end up with. We place it right below the direction cosine section from the previous step.

*else if (attitudeDisplayType == "MRPs") { #ifdef DEBUG_ATTITUDE_READ_ONLY MessageInterface::ShowMessage( " ....... Attitude::ReadOnly in MRPs section\n"); #endif if ((id == EULER_ANGLE_1) || (id == EULER_ANGLE_2) || (id == EULER_ANGLE_3) || (id == Q_1) || (id == Q_2) || (id == Q_3) || (id == Q_4) || (id == DCM_11) || (id == DCM_12) || (id == DCM_13) || (id == DCM_21) || (id == DCM_22) || (id == DCM_23) || (id == DCM_31) || (id == DCM_32) || (id == DCM_33)) return true; }*

- There is only one attitude display type left. That is
**"else" Euler Angles**. In that section we add a line for MRPs. Here is the result.

*if ((id == Q_1) || (id == Q_2) || (id == Q_3) || (id == Q_4) || (id == MRP_1) || (id == MRP_2) || (id == MRP_3) || (id == DCM_11) || (id == DCM_12) || (id == DCM_13) || (id == DCM_21) || (id == DCM_22) || (id == DCM_23) || (id == DCM_31) || (id == DCM_32) || (id == DCM_33)) return true;*

- Next we go to the
**real Attitude::GetRealParameter(const Integer id) const**method, and add three MRP lines. Just below**Euler Angles**we add the following.

*if (id == MRP_1) { (const_cast(this))->UpdateState("MRPs"); return mrps(0); } if (id == MRP_2) { (const_cast(this))->UpdateState("MRPs"); return mrps(1); } if (id == MRP_3) { (const_cast(this))->UpdateState("MRPs"); return mrps(2); }*

- Next we got to the
**Set Real Parameter**method. Syntax for this method is as follows.

*Real Attitude::SetRealParameter(const Integer id, const Real value) Right after EULER_ANGLE_3, we add the following. if (id == MRP_1) { (const_cast(this))->UpdateState("MRPs"); mrps(0) = value; inputAttitudeType = GmatAttitude::MODIFIED_RODRIGUES_PARAMETERS_TYPE; if (isInitialized) needsReinit = true; return mrps(0); } if (id == MRP_2) { (const_cast(this))->UpdateState("MRPs"); mrps(1) = value; inputAttitudeType = GmatAttitude::MODIFIED_RODRIGUES_PARAMETERS_TYPE; if (isInitialized) needsReinit = true; return mrps(1); } if (id == MRP_3) { (const_cast(this))->UpdateState("MRPs"); mrps(2) = value; inputAttitudeType = GmatAttitude::MODIFIED_RODRIGUES_PARAMETERS_TYPE; if (isInitialized) needsReinit = true; return mrps(2); }*

- Next we add to another
**Set Real Parameter**method. This method is different from the one above because it checks the range of the input parameter index. The calling syntax is as follows.

*Real Attitude::SetRealParameter(const Integer id, const Real value, const Integer index) Right after QUATERNION we add the following. if (id == MRPS) { if ((index < 0) || (index > 2)) { throw AttitudeException("Error: the MRPs index is out-of-range\n"); } mrps(index) = value; inputAttitudeType = GmatAttitude::MODIFIED_RODRIGUES_PARAMETERS_TYPE; #ifdef DEBUG_ATTITUDE_SET_REAL MessageInterface::ShowMessage(" ... set MRP[%d] = %12.10f\n", index, value); MessageInterface::ShowMessage(" ... MRP = %s\n", quaternion.ToString().c_str()); #endif if (isInitialized) needsReinit = true; return mrps(index); }*

- Next is the
**const****Rvector& Attitude::GetRvectorParameter(const Integer id) const**method. Just after**QUATERNION**we add the following.

*if (id == MRPS) { (const_cast(this))->UpdateState("MRPs"); vec3 = mrps; return vec3; }*

- Next is the
**Set Rvector Parameter**method. The calling syntax is as follows.

*const Rvector& Attitude::SetRvectorParameter(const Integer id, const Rvector &value) Just after QUATERNION we add the following. if (id == MRPS) { #ifdef DEBUG_ATTITUDE_SET MessageInterface::ShowMessage("In Attitude::SetRvector, setting MRPs\n"); #endif if (sz != 3) throw AttitudeException( "Incorrectly sized Rvector passed in for MRPs."); ValidateMRPs(value); for (i=0;i<3;i++) mrps = value; quaternion = Attitude::ToQuaternion(mrps); cosMat = Attitude::ToCosineMatrix(quaternion); inputAttitudeType = GmatAttitude::MODIFIED_RODRIGUES_PARAMETERS_TYPE; if (isInitialized) needsReinit = true; return mrps; }*

- Next we add two lines to the
**Set String Parameter**method. There are two lines where value is being compared to string values. We add MRPs to the second line to get the following.

*if ((value != "Quaternion" ) && (value != "DirectionCosineMatrix") && (value != "EulerAngles") && (value != "MRPs"))*

- In the same method, four lines below we add MRPs to get the following.

*"\"Quaternion\" \"DirectionCosineMatrix\" \"EulerAngles\" \"MRPs\"");*

- In the same method, near the bottom, just after the comment that says to
**"handle the array values"**, we add MRPs to get the following two lines.

*if ((id == QUATERNION) || (id == EULER_ANGLES) || (id == DIRECTION_COSINE_MATRIX) || (id == MRPS) || (id == ANGULAR_VELOCITY) || (id == EULER_ANGLE_RATES))*

- Next we write and insert our own method,
**bool****ValidateMRPs**. At this point we may be missing some charateristics such as maximum and minimum possible values that we could use to enhance this algorithm, but at least we have a start.

*//-------------------------------------------------------------------------- // bool ValidateMRPs(const Rvector &mrps) //-------------------------------------------------------------------------- /** * This method validates the MRPs. * * @param quaternion to validate * * @return flag indicating whether or not the input is a valid quaternion * */ //-------------------------------------------------------------------------- bool Attitude::ValidateMRPs(const Rvector &mrps) { // There may be some more qualities of MRPs we can check if (mrps.GetSize() != 3) { throw AttitudeException( "The MRP vector must have 3 elements.\n"); } return true; }*

- We are at long last down to augmenting our final method. This is the
**UpdateState**method. This method updates the attitude in the representation specified. We start in the section where**rep == "Quaternion"**. We add the following logic.

*else if (inputAttitudeType == GmatAttitude::MODIFIED_RODRIGUES_PARAMETERS_TYPE) quaternion = Attitude::ToQuaternion(mrps); Next we go to rep == "EulerAngles". We add the following logic. else if (inputAttitudeType == GmatAttitude::MODIFIED_RODRIGUES_PARAMETERS_TYPE){ quaternion = Attitude::ToQuaternion(mrps); eulerAngles = Attitude::ToEulerAngles(quaternion, (Integer) eulerSequenceArray.at(0), (Integer) eulerSequenceArray.at(1), (Integer) eulerSequenceArray.at(2));}*

- Where
**rep == "DirectionCosineMatrix"**we add the following.

*else if (inputAttitudeType == GmatAttitude::MODIFIED_RODRIGUES_PARAMETERS_TYPE){ quaternion = Attitude::ToQuaternion(mrps); cosMat = ToCosineMatrix(quaternion);}*

- Finally we add an entire section for
**rep == "MRPs"**. We add the following.

*else if (rep == "MRPs") { if (inputAttitudeType == GmatAttitude::QUATERNION_TYPE) mrps = ToMRPs(quaternion); else if (inputAttitudeType == GmatAttitude::DIRECTION_COSINE_MATRIX_TYPE){ quaternion = Attitude::ToQuaternion(cosMat); mrps = ToMRPs(quaternion);} else if (inputAttitudeType == GmatAttitude::EULER_ANGLES_AND_SEQUENCE_TYPE){ cosMat = ToCosineMatrix(eulerAngles, (Integer) eulerSequenceArray.at(0), (Integer) eulerSequenceArray.at(1), (Integer) eulerSequenceArray.at(2)); quaternion = ToQuaternion(cosMat); mrps = ToMRPs(quaternion); } }*

- Finally we compile, rebuild GMAT, and test its functionality.

##### Step 5:

Next we will modify **AttitudePanel.hpp**. This is found in the **src\GUI\spacecraft** directory.

- Halfway down the file, in the declaration of the
**AttitudePanel: public wxPanel**class, in the**private:**section, in the**// string versions of values in text boxes**section, just after**wxString *eulerAngles[3];**, add the following.

*wxString *MRPs[3];*

- Just below this, in the
**// local values of attitude data**section, right after the declaration of**Rvector3 ea;**, add the following.

*vector3 mrp;*

- Just below this, in the
**/// modification flags**section, right after the declaration of**bool****eaModified[3];**, add the following.

*bool mrpModified[3];*

- Just below this, after the
**void DisplayDCM();**method declaration, add the following.

*void DisplayMRPs();*

- Just below this, after the
**void UpdateCosineMatrix();**method declaration, add the following.

*void UpdateMRPs();*

- Add MRPs to the
**StateType**enumeration. It should now look like this.

*enum StateType { EULER_ANGLES = 0, QUATERNION, DCM, MRPS, attStateTypeCount, };*

- Rebuild GMAT with
**AttitudePanel.hpp**updated. Test functionality. At this point it should be broken because we will have changed some of the parameters used in the methods in**AttitudePanel.cpp**. Everything will have compiled, but there will be linking errors. We can track down each of the linking errors, or go to the next step and modify**AttitudePanel.cpp**. We will take the second option.

##### Step 6:

Next we modify **AttitudePanel.cpp**. It is in the same directory as **AttitudePanel.hpp**.

- In the
**const****std::string AttitudePanel::STATE_TEXT[attStateTypeCount]**method, just below**"DirectionCosineMatrix"**add MRPs. The method should now look like this.

*// These labels show up in the following location in the GUI: // - "Spacecraft" Dialog Box // - "Attitude" Tab // - "Attitude Initial Conditions" Static Box // - "Attitude State Type" Combo Box const std::string AttitudePanel::STATE_TEXT[attStateTypeCount] = { "EulerAngles", "Quaternion", "DirectionCosineMatrix", "MRPs", };*

- In the
**AttitudePanel::Create()**method, in the loop under the heading titled**//arrays to hold temporary values**, add MRPs. The loop should now look like this.

*// arrays to hold temporary values unsigned int x; for (x = 0; x < 3; ++x) { eulerAngles[x] = new wxString(); eulerAngleRates[x] = new wxString(); quaternion[x] = new wxString(); MRPs[x] = new wxString(); angVel[x] = new wxString(); } *

The observant reader will note that **cosineMatrix[x]** was replaced by **MRPs[x]**. This is because the full 9 element array was being populated in three separate statements. Dunn took the final loop and changed its indices to fully populate **cosineMatrix** in a single location. There is still some awkwardness remaining in this method with the 4 element quaternion string array, but we will leave fixing that to the student as an exercise.

- In the
**AttitudePanel::LoadData()**method, just below the else if logic branch for**attStateType == "Quaternion"**, just below the**DisplayQuaternion()**statement, add the following code.

*else if (attStateType == "MRPs") { Rvector MRPVal = theAttitude->GetRvectorParameter("MRPs"); for (x = 0; x < 3; ++x) { *MRPs[x] = theGuiManager->ToWxString(MRPVal[x]); mrp[x] = MRPVal[x]; } DisplayMRPs(); }*

- In the
**AttitudePanel::SaveData()**method, in the logic branch for the condition**if (seqModified || isNewAttitude)**just below the statement which says**useAttitude->SetRvectorParameter("Quaternion", q);**add the following.

*else if (attStateType == stateTypeArray[MRPS]) useAttitude->SetRvectorParameter("MRPs", mrp);*

- In the same
**AttitudePanel::SaveData()**method, in the logic branch**if (stateModified || isNewAttitude)**just below the statement which says**useAttitude->SetRvectorParameter("Quaternion", q);**add the following.

*else if (attStateType == stateTypeArray[MRPS]) useAttitude->SetRvectorParameter("MRPs", mrp); *

This may seem identical to the process in the previous step. The code is the same, but the "if" statement is different. Inspect them closely!

- In the
**AttitudePanel::IsStateModified**method, in the logic branch**if ((which=="State")||(which=="Both"))**just below the statement which says**if (qModified[ii]) return true;**add the following.

*else if (attStateType == stateTypeArray[MRPS]) { for (Integer ii = 0; ii < 3; ii++) if (mrpModified[ii]) return true; }*

- In the
**AttitudePanel::ResetStateFlags**method, just below the statement which says**qModified[ii] = false;**add the following.

*for (Integer ii = 0; ii < 3; ii++) mrpModified[ii] = false;*

- In the
**AttitudePanel::ValidateState**method, just below the statement which says**else q[3] = tmpVal;**we need to add an entire section on MRPs. It should look like this.

*else if (attStateType == stateTypeArray[MRPS]) { if (mrpModified[0]) { strVal = st1TextCtrl->GetValue(); if (!theScPanel->CheckReal(tmpVal, strVal, "MRP 1", "Real Number")) retval = false; else mrp[0] = tmpVal; } if (mrpModified[1]) { strVal = st2TextCtrl->GetValue(); if (!theScPanel->CheckReal(tmpVal, strVal, "MRP 2", "Real Number")) retval = false; else mrp[1] = tmpVal; } if (mrpModified[2]) { strVal = st3TextCtrl->GetValue(); if (!theScPanel->CheckReal(tmpVal, strVal, "MRP 3", "Real Number")) retval = false; else mrp[2] = tmpVal; } }*

- In the
**AttitudePanel::OnStateTextUpdate**method, after the section on Quaternions, add the following.

*else if (attStateType == STATE_TEXT[MRPS]) { if (st1TextCtrl->IsModified()) mrpModified[0] = true; if (st2TextCtrl->IsModified()) mrpModified[1] = true; if (st3TextCtrl->IsModified()) mrpModified[2] = true; }*

- In the
**AttitudePanel::OnStateTypeSelection**method, after the**DisplayDCM();**statement, add the following.

*else if (newStateType == stateTypeArray[MRPS]) DisplayMRPs();*

- The next step is to write a method called
**void AttitudePanel::DisplayMRPs()**. This method is based on the similar method for displaying Euler Angles. It is an entire standalone method rather than a modification of an existing method. Please see the actual code for this method because it is rather long for a checklist like this. - In the
**AttitudePanel::UpdateCosineMatrix()**method, just below the conversion to**dcmat**from**EULER_ANGLES**, add a conversion to the**dcmat**from**MRPS**. It will go through quaternions as an intermediate stage as follows.

*else if (attStateType == stateTypeArray[MRPS]) { q = Attitude::ToQuaternion(mrp); dcmat = Attitude::ToCosineMatrix(q); }*

- In the
**AttitudePanel::UpdateQuaternion()**method, just below the conversion to**q**from**EULER_ANGLES**, add a conversion to**q**from**MRPS**. It will look like this.

*else if (attStateType == stateTypeArray[MRPS]) { q = Attitude::ToQuaternion(mrp); }*

- In the
**AttitudePanel::UpdateEulerAngles()**method, just below converstion to**ea**from**QUATERNION**, add a conversion to**ea**from**MRPS**. It will look like this.

*else if (attStateType == stateTypeArray[MRPS]) { q = Attitude::ToQuaternion(mrp); ea = Attitude::ToEulerAngles(q, (Integer) seq[0], (Integer) seq[1], (Integer) seq[2]) * GmatMathUtil::DEG_PER_RAD; }*

- Next add the method
**AttitudePanel::UpdateMRPs()**. Since this is an entire method, please go see the example in the code. It parallels all the other update methods, but this method updates MRPs. - Finally, compile
**AttitudePanel.cpp**. Then test functionality. This will require linking which hopefully will have no errors now that all the methods in**AttitudePanel.cpp**match their declarations in**AttitudePanel.hpp**.

##### Step 7:

At this point the Attitude Tab of the Spacecraft Dialog Box will let the user choose MRPs.

If the user inputs MRPs, they can be converted to Quaternions, Euler Angles, or the DC Matrix. If the user leaves the representation as MRPs and saves a script, that script will show MRPs. Here is an example.

*GMAT ISS.AttitudeDisplayStateType = 'MRPs'; GMAT ISS.AttitudeRateDisplayStateType = 'AngularVelocity'; GMAT ISS.AttitudeCoordinateSystem = 'LVLH'; GMAT ISS.EulerAngleSequence = '321'; GMAT ISS.MRP1 = 0.4142135623; GMAT ISS.MRP2 = 0; GMAT ISS.MRP3 = 0; GMAT ISS.AngularVelocityX = 0; GMAT ISS.AngularVelocityY = 0; GMAT ISS.AngularVelocityZ = 0;*

Figure 1 - MRPs for ISS Showing 90 Deg Roll

##### Step 8:

The picture above shows the MRPs from a GMAT script with a model of the ISS which has been rolled. Since the ISS model started with no attitude relative to the ECI reference frame, shown with Red Green and Blue axes, it rolled around both its own X-Axis and the red ECI X-Axis.

Figure 2 - ISS MRPs Converted to Other Three Attitude Representations

##### Step 9:

The picture above shows the original MRPs converted into the other three basic GMAT attitude representations. Note that in the first figure, the Euler Angle Sequence is **321**, or **Yaw-Pitch-Roll**. In the Euler Angles display, we see that the third angle, or **roll**, is **90°**. This means the ISS is rolled on its side but it's X-Axis is still aligned with ECI-X.

##### Step 10:

Next we modify methods and add parameters so that GMAT can output MRPs in data files and plot them in its standard XY-Plot routines. We will start by listing Linda Jun's suggested steps.

- In
**base/parameter/AttitudeParameters.hpp**, add a new Parameter as a new class. This file declares each related**Parameter**class. If**Parameter**returns a scalar value, make it derive from**AttitudeReal**class. If you want to see it in the XY plot, it has to be a scalar Parameter. The following for MRPs was added directly below the declarations for Euler Angles. The excerpt below only shows the code for MRP1. There are two more similar blocks for MRP2 and MRP3.

*class GMAT_API MRP1 : public AttitudeReal { public: ** MRP1(const std::string &name = "", GmatBase *obj = NULL); MRP1(const MRP1 ©); MRP1& operator=(const MRP1 &right); virtual ~MRP1(); ** // methods inherited from Parameter virtual bool Evaluate(); ** // methods inherited from GmatBase virtual GmatBase* Clone(void) const; ** protected: };*

- Choose an all caps enumeration that is different from the parameter name from the step above. In
**base/parameter/AttitudeData.hpp**, add**ATTPARENUM**to enum list. For this example, we will make the Modified Rodriguez Parameter enumerations**MRP_1**,**MRP_2**, and**MRP_3**.

- In
**base/parameter/AttitudeParameters.cpp**, add**Parameter**methods. Parameter names must be unique and can be different from class name. In**Evaluate()**, change to call**AttitudeData::GetAttitudeReal(ATTPARENUM)**, where**ATTPARENUM**is the**enum**named**constant**. For this example the MRP classes will be**MRP1**,**MRP2**, and**MRP3**. We won't show examples of each method here. Please see the code.

- In
**base/parameter/AttitudeData.cpp**, add implementation for**ATTPARENUM**in**GetAttitudeReal()**. For this example we added implementation immediately below the conversion for Euler Angles. It looks like this.

*if ((item >= MRP_1) && (item <= MRP_3)) { Rvector quat = Attitude::ToQuaternion(cosMat); Rvector3 mrp = Attitude::ToMRPs(quat); return mrp[item - MRP_1]; }*

- In
**base/factory/ParameterFactory.cpp**, in**CreateParameter()**, under**//AttitudeParameters**, add the following.

*if(ofType == "NewParamName") return new NewParamClass(withName); For the current MRP example, we add the following under Euler Angles. if (ofType == "MRP1") return new MRP1(withName); if (ofType == "MRP2") return new MRP2(withName); if (ofType == "MRP3") return new MRP3(withName);*

- In
**ParameterFactory::ParameterFactory():Factory(Gmat::PARAMETER)**, add**"NewParamName"**into**creatables**. For the MRP example this is found just below the**push_back**calls for**Euler Angles**. It looks like this.

*creatables.push_back("MRP1"); creatables.push_back("MRP2"); creatables.push_back("MRP3");*

- In
**base/executive/Moderator.cpp**, in**CreateDefaultMission()**, under the heading**//Attitude Parameters**, add**CreateParameter("NewParamName", "DefaultSC.NewParamName")**; so new Parameter can show up in the ParameterSelect DialogBox. For our example, these are immediately below**Euler Angles**and look like this.

*CreateParameter("MRP1", "DefaultSC.MRP1"); CreateParameter("MRP2", "DefaultSC.MRP2"); CreateParameter("MRP3", "DefaultSC.MRP3");*

##### Step 11:

Next we compile GMAT and test it. If it works, it can give MRPs plotted in the XY-Plot output screen as shown below for the ISS. In the figure below the ISS model is no longer rolled around its own X-axis. It is still shown at an interesting angle because now it is aligned with both the velocity vector and nadir for an orbit that is inclined at 50 degrees.

Figure 3 - Plotting MRPs for Nadir Stabilized, And No Longer Rolled, International Space Station