Difference between revisions of "Vessel Tutorial 1"

From OrbiterWiki
Jump to navigation Jump to search
(Added category.)
 
(17 intermediate revisions by 6 users not shown)
Line 1: Line 1:
=== Vessel Tutorial (Work in progress) ===
 
 
 
This article intends to guide you through the construction of a new Vessel DLL, by using the sample code from the OrbiterSDK.  
 
This article intends to guide you through the construction of a new Vessel DLL, by using the sample code from the OrbiterSDK.  
  
 
This documents my adventure transforming the stock ShuttlePB into a reasonably accurate model of the Surveyor lunar lander. There was a model on OrbitHangar, but it had wildly inaccurate mechanics, due partly to the fact that it was based on Vinka's Spacecraft.dll.
 
This documents my adventure transforming the stock ShuttlePB into a reasonably accurate model of the Surveyor lunar lander. There was a model on OrbitHangar, but it had wildly inaccurate mechanics, due partly to the fact that it was based on Vinka's Spacecraft.dll.
  
This tutorial assumes you have a working compiler which you know creates valid working DLLs from the OrbiterSDK sample code. I happen to use the free Microsoft command-line compiler, together with an editor called TextPad. Setting up the free compiler and the editor is beyond the scope of this document. Check the rest of this Wiki, or the Orbiter forums.
+
This tutorial assumes you have a working compiler which you know creates valid working DLLs from the OrbiterSDK sample code. I originally used the free Microsoft command-line compiler VC++ 2003, together with an editor called TextPad. I now use the [[Free Compiler Setup|free Microsoft IDE VC++ 2005]]. Setting this up is beyond the scope of this document. Check the rest of this Wiki, or the Orbiter forums.
  
=== My goal spacecraft: Surveyor ===
+
= The Goal Spacecraft: Surveyor =
  
 
Surveyor was a series of unmanned lunar landers launched by NASA in 1965-1967, before the first Apollo. It is a particularly simple, yet interesting and highly successful design.
 
Surveyor was a series of unmanned lunar landers launched by NASA in 1965-1967, before the first Apollo. It is a particularly simple, yet interesting and highly successful design.
  
 
Functionally, Surveyor consists of a large solid-fueled main retro-rocket, three medium-sized throttleable liquid fueled vernier engines, and six cold-gas Reaction Control System (RCS) attitude control thrusters.
 
Functionally, Surveyor consists of a large solid-fueled main retro-rocket, three medium-sized throttleable liquid fueled vernier engines, and six cold-gas Reaction Control System (RCS) attitude control thrusters.
 +
 +
The Surveyor flight plan in its final minutes is one exciting ride, and obviously not good for humans. There is no possiblity of abort (two of the seven Surveyors, 2 and 4, crashed). The maximum acceleration is 9G's. And, it is very difficult to fly manually. It's a perfect job for a machine.
  
 
Surveyor is launched on top of an Atlas-Centaur rocket, on a direct impact trajectory to the moon. Impact will occur after about 66 hours. About 16 hours after launch, it performs a single mid-course correction maneuver, which aims it to precisely impact at the desired landing point.  
 
Surveyor is launched on top of an Atlas-Centaur rocket, on a direct impact trajectory to the moon. Impact will occur after about 66 hours. About 16 hours after launch, it performs a single mid-course correction maneuver, which aims it to precisely impact at the desired landing point.  
  
Thirty minutes before impact, Surveyor maneuvers into a retrograde attitude. From here, its '''Altitude Marking Radar''' (AMR) will eventually be able to see the surface and measure the distance. The AMR locks on to the surface at about 200km, one minute before impact. Once the altitude reaches a pre-determined value called the '''Altitude Mark''', it starts a short timer.  
+
Thirty minutes before impact, Surveyor maneuvers into a retrograde attitude. From here, its Altitude Marking Radar (AMR) will eventually be able to see the surface and measure the distance. The AMR locks on to the surface at about 200km, a little more than one minute before impact. Once the altitude reaches a pre-determined value called the altitude mark, it starts a short timer.  
  
At the altitude mark, the spacecraft is hurtling towards the ground at over 2.5km/s and will impact within a minute. For Surveyor 1, the altitude mark was about 110km, and the timer was about 7 seconds. Once the timer expires, the vernier engines light up, then the main retro ignites for its 40 second burn. The main retro gets rid of 2.3km/s of speed, and leaves the spacecraft between 5km and 20km above the surface and travelling downwards at between zero and 230m/s. During this stage, the vernier engines are used to stabilize the spacecraft and compensate for any thrust misalignment in the main retro. The AMR is automatically jettisoned at main retro ignition, as it is no longer needed. In fact, it is stuffed into the nozzle of the main retro, and is blasted out by the retro ignition.
+
At the altitude mark, the spacecraft is hurtling towards the ground at over 2.5km/s and will impact in less than one minute. For Surveyor 1, the altitude mark was about 110km, and the timer was about 7 seconds. Once the timer expires, the vernier engines light up, then one second later the main retro ignites for its 40 second burn. The main retro gets rid of 2.3km/s of speed, and leaves the spacecraft between 5km and 20km above the surface and travelling downwards at between zero and 230m/s. During this stage, the vernier engines are used to stabilize the spacecraft and compensate for any thrust misalignment in the main retro. The AMR is automatically jettisoned at main retro ignition, as it is no longer needed. In fact, it is stuffed into the nozzle of the main retro, and is blasted out by the retro ignition.
  
The spacecraft then drops the spent retro motor and continues down on the vernier engines. Using a seperate four-beam Doppler radar and a simple analog computer, it throttles the engines such that it will reach a downward velocity of about 1.3m/s about 4m above the surface. It then cuts its engines and falls the rest of the way.
+
The spacecraft then drops the spent retro motor and continues down on the vernier engines. Using a separate four-beam Doppler radar and a simple analog computer, it throttles the engines such that it will reach a downward velocity of about 1.3m/s about 4m above the surface. It then cuts its engines and falls the rest of the way.
  
=== Why a custom DLL ===
+
= Why a custom DLL =
  
 
Many spacecraft can and in fact have been simulated with Vinka's spacecraft.dll. This method uses a text file to describe a spacecraft, and a general purpose DLL to interpret it.
 
Many spacecraft can and in fact have been simulated with Vinka's spacecraft.dll. This method uses a text file to describe a spacecraft, and a general purpose DLL to interpret it.
Line 29: Line 29:
 
I hope that this article is useful to anyone trying to write a custom vessel DLL. I find it much easier to understand a concept when 1) I need to use it and 2) There is a well-documented example of using the concept. You have to provide the need. I provide the example.
 
I hope that this article is useful to anyone trying to write a custom vessel DLL. I find it much easier to understand a concept when 1) I need to use it and 2) There is a well-documented example of using the concept. You have to provide the need. I provide the example.
  
=== The Rules ===
+
= The Rules =
  
 +
# '''''ALWAYS''''' test each change, no matter how small
 +
# For historical or real vessels, get good reference material
 +
# '''''NEVER''''' proceed to the next change if the last one doesn't work yet
 
# Start with something you know works
 
# Start with something you know works
 +
# '''''ALWAYS''''' '''''ALWAYS''''' test each change, no matter how small
 +
# There is NO rule 6
 +
# '''''NEVER''''' '''''NEVER''''' proceed to the next change if the last one doesn't work yet
 
# Only make one change at a time
 
# Only make one change at a time
 
# '''''ALWAYS''''' '''''ALWAYS''''' '''''ALWAYS''''' test each change, no matter how small
 
# '''''ALWAYS''''' '''''ALWAYS''''' '''''ALWAYS''''' test each change, no matter how small
# For historical or real vessels, get good reference material
+
# '''''NEVER''''' '''''NEVER''''' '''''NEVER''''' proceed to the next change if the last one doesn't work yet
# '''''NEVER''''' '''''NEVER''''' '''''NEVER''''' proceed to the next change if the last one is broken
+
 
# There is NO rule 6
+
= Historical Spacecraft Documentation =
 +
 
 +
The best place to get references for NASA spacecraft is the [http://ntrs.nasa.gov/ NASA Technical Reports Server]. For this vessel, applicable reports are things such as:
 +
*[http://ntrs.nasa.gov/archive/nasa/casi.ntrs.nasa.gov/19670029911_1967029911.pdf Surveyor Spaceraft A-21A Model Description] for spacecraft systems and such.
 +
*[http://ntrs.nasa.gov/archive/nasa/casi.ntrs.nasa.gov/19650014299_1965014299.pdf Final Report: Surveyor Vernier Thrust Chamber Assembly Development, Phase III] All the other documents refer to the fact that the Surveyor Vernier engines' specific impulse varies with thrust, but never says in what manner. Page 86 shows an Isp vs Thrust curve for the vernier engines
 +
*[http://ntrs.nasa.gov/archive/nasa/casi.ntrs.nasa.gov/19660026658_1966026658.pdf Surveyor 1 mission report. Part 1 - Mission description and performance] Tells about the actual performance of the Surveyor 1 lander in excruciating detail. It includes such specifics as the exact launch window and translunar trajectory, before and after the midcourse maneuver.
 +
*[http://ntrs.nasa.gov/archive/nasa/casi.ntrs.nasa.gov/19660082318_1966082318.pdf Surveyor Terminal Guidance] Outlines a rather simple and surprisingly robust system for landing a Surveyor spacecraft with a stupid analog computer and a 4-beam landing radar
 +
*[http://www.google.com/help/features.html#calculator Google Calculator] Just type some calculation in the search box. The calculator especially excels at unit conversions. You don't have to go to that link, you can type a calculation in the Google search box on its [http://www.google.com/ main page].
  
=== The Starting Point ===
+
= The Starting Point =
  
 
So, how do you start with something that works, if you are creating a brand new vessel? Good question. The answer is to look through the samples in Orbiter\OrbiterSDK\Samples for the ship which is closest to the one you have in mind.
 
So, how do you start with something that works, if you are creating a brand new vessel? Good question. The answer is to look through the samples in Orbiter\OrbiterSDK\Samples for the ship which is closest to the one you have in mind.
Line 44: Line 57:
 
Sometimes nothing is particularly close. It was in my case. In this case, just start with the simplest possible ship, the ShuttlePB. Make a copy of this folder, then rename it to your new vessel name. If you are using Visual C++, you probably need to do some weird stuff to rename the project and all the files in it. I use the free compiler, so the project files are no good to me. If you use it also, feel free to just delete all the files except for ShuttlePB.cpp, and rename it to a new name, like Surveyor.cpp.
 
Sometimes nothing is particularly close. It was in my case. In this case, just start with the simplest possible ship, the ShuttlePB. Make a copy of this folder, then rename it to your new vessel name. If you are using Visual C++, you probably need to do some weird stuff to rename the project and all the files in it. I use the free compiler, so the project files are no good to me. If you use it also, feel free to just delete all the files except for ShuttlePB.cpp, and rename it to a new name, like Surveyor.cpp.
  
Now, edit Surveyor.cpp. Do a global search-and-replace to change all references from ShuttlePB to Surveyor. Go ahead and modify the copyright message at the top, to say that this is a work derived from the original ShuttlePB. By the time we are done, almost all the old ShuttlePB code will be replaced.
+
Also, copy the source spacecraft's config file. The config file for Surveyor is shown here, and includes an attachment point, for use for attachment to a future launch vehicle.
  
In keeping with Rule 3, compile this file, copy the resulting DLL to the Orbiter\Modules, and use the following scenario. It should run perfectly, and look and fly exactly like the old ShuttlePB. If at any point in this tutorial, if it doesn't work
+
Classname=Surveyor
 +
Module=Surveyor
 +
 +
; === Attachment specs ===
 +
BEGIN_ATTACHMENT
 +
P 0 0 0  0 -1 0  0 0 1  XS
 +
END_ATTACHMENT
  
<pre><nowiki>
+
Now, edit Surveyor.cpp. Do a global search-and-replace to change all references from ShuttlePB to Surveyor. Go ahead and modify the copyright message at the top, to say that this is a work derived from the original ShuttlePB. By the time we are done, almost all the old ShuttlePB code will be replaced.
BEGIN_ENVIRONMENT
 
  System Sol
 
  Date MJD 51982.6685767396
 
END_ENVIRONMENT
 
  
BEGIN_FOCUS
+
In keeping with Rules 1, 3, 5, 7, 9, and 10, compile this file, copy the resulting DLL to the Orbiter\Modules, and use the following scenario. It should run perfectly, and look and fly exactly like the old ShuttlePB.
  Ship Surveyor1
 
END_FOCUS
 
  
BEGIN_CAMERA
+
BEGIN_ENVIRONMENT
  TARGET Surveyor1
+
  System Sol
  FOV 50.00
+
  Date MJD 51982.6685767396
END_CAMERA
+
END_ENVIRONMENT
 +
 +
BEGIN_FOCUS
 +
  Ship Surveyor1
 +
END_FOCUS
 +
 +
BEGIN_CAMERA
 +
  TARGET Surveyor1
 +
  FOV 50.00
 +
END_CAMERA
 +
 +
BEGIN_SHIPS
 +
Surveyor1:Surveyor
 +
  STATUS Landed Moon
 +
  BASE Brighton Beach:1
 +
  HEADING 90.00
 +
  PRPLEVEL 0:1.000
 +
END
 +
END_SHIPS
  
BEGIN_SHIPS
+
If, at any point in this tutorial, it doesn't work, track down and try to identify what could have gone wrong. Since the most common symptom of failure is a Crash to Desktop (CTD) you won't have much help debugging, so keeping the changes small from version to version will help out a lot.
Surveyor1:Surveyor
 
  STATUS Landed Moon
 
  BASE Brighton Beach:1
 
  HEADING 90.00
 
  PRPLEVEL 0:1.000
 
END
 
END_SHIPS
 
</nowiki></pre>
 
  
=== Meshes ===
+
= Meshes =
  
Creating meshes is beyond the scope of this document. Since I am in a hurry and not primarily interested in artwork, I borrowed the mesh from Surveyor1-1.zip by Jim Williams, posted on OrbitHangar.  
+
Creating meshes is beyond the scope of this document. Since I am in a hurry and not primarily interested in artwork, I borrowed the mesh from ''Surveyor1-1.zip'' [http://www.orbithangar.com/download.php?ID=859] by Jim Williams, posted on OrbitHangar.  
  
If you are following along, get that add-on, and get the meshes out of it. Rename Surveyor-deployed.msh as Surveyor-Lander.msh . Copy this mesh to Orbiter\Meshes , and copy all the .dds files to Orbiter\Textures .
+
If you are following along, get that add-on, and get the meshes out of it. Rename ''Surveyor-deployed.msh'' as ''Surveyor-Lander.msh''. Copy this mesh to Orbiter\Meshes, and copy all the .dds files to Orbiter\Textures.
  
Now, to use this mesh. In Surveyor.cpp, look for the method Surveyor::clbkSetClassCaps . Near the end, change the line (Note that it doesn't say ShuttlePB, remember that you searched-and-replaced that out.)
+
Now, to use this mesh. In Surveyor.cpp, look for the method Surveyor::clbkSetClassCaps. Near the end, change the line (Note that it doesn't say ShuttlePB, remember that you searched-and-replaced that out)
  
<pre><nowiki>
 
 
   // visual specs
 
   // visual specs
 
   AddMesh ("Surveyor");
 
   AddMesh ("Surveyor");
</nowiki></pre>
 
  
 
to
 
to
  
<pre><nowiki>
 
 
   // visual specs
 
   // visual specs
 
   AddMesh("Surveyor-Lander");
 
   AddMesh("Surveyor-Lander");
</nowiki></pre>
 
  
Compile and test it. Notice that the spacecraft now looks like Surveyor, but it flies like ShuttlePB. Also note that the legs are not on the surface. Go ahead and take Surveyor for a spin, and put it in orbit around the moon. It will be much easier to test the verner and RCS engines in space.
+
Compile and test it. Notice that the spacecraft now looks like Surveyor, but it flies like ShuttlePB. Also note that the legs are not on the surface. Go ahead and take Surveyor for a spin, and put it in orbit around the moon. It will be much easier to test the vernier and RCS engines in space. Do a QuickSave, rename the QuickSave to something sensible like ''Surveyor in Orbit.scn'' and use the new scenario from now on.
  
=== Vehicle coordinate system ===
+
= Vehicle coordinate system =
  
As described in the Orbiter API guide, vessels use a left-handed coordinate system. Even though Surveyor looks nothing like and is flown nothing like an airplane, an airplane is still a useful model to describe the coordinate system. Imagine yourself sitting in the pilot's seat of an airplane. The X axis is the "left-to-right" axis. +X points out to the right wing, and -X points out to the left wing. The Y axis is the "up-to-down" axis. +Y points up, in the direction of the vertical tail fin, and -Y points down in the direction of the wheels. The Z axis is the "front-to-back" axis. +Z points to the nose, and -Z points back to the aft end of the plane. In an conventional airplane, the engines thrust towards +Z.
+
As described in the Orbiter API guide, vessels use a left-handed coordinate system. Even though Surveyor looks nothing like and is flown nothing like an airplane, an airplane is still a useful model to describe the coordinate system. Imagine yourself sitting in the pilot's seat of an airplane. The X axis is the "left-to-right" axis. +X points out to the right wing, and -X points out to the left wing. The Y axis is the "up-to-down" axis. +Y points up, in the direction of the vertical tail fin, and -Y points down in the direction of the wheels. The Z axis is the "front-to-back" axis. +Z points to the nose, and -Z points back to the aft end of the plane. In an conventional airplane or rocket, the engine's thrust vector points towards +Z. The exhaust streams out the back towards -Z. We are going to use this coordinate system, also, as it almost matches the historical Surveyor. On it, +Z and -Z are reversed and the whole thing is a right-handed system.
  
=== Vernier Engines ===
+
So, imagine yourself again sitting in the pilot seat of Surveyor. The engines and legs are behind you, on the back of your seat, and the antenna and solar panel stick out ahead of you. When you are on approach to landing, you will have your back to the surface of the Moon and your face straight up towards the stars.
 +
 
 +
= Vernier Engines =
  
 
These will be the "main" engines controllable with the NumPad+ key. Even though these vernier engines are the very reason we are using a custom DLL rather than spacecraft.dll, we are not going to do anything fancy to them yet.
 
These will be the "main" engines controllable with the NumPad+ key. Even though these vernier engines are the very reason we are using a custom DLL rather than spacecraft.dll, we are not going to do anything fancy to them yet.
Line 109: Line 130:
 
Find this code in Surveyor::clbkSetClassCaps .  
 
Find this code in Surveyor::clbkSetClassCaps .  
  
<pre><nowiki>
+
void Surveyor::clbkSetClassCaps (FILEHANDLE cfg)
void Surveyor::clbkSetClassCaps (FILEHANDLE cfg)
+
{
{
+
  THRUSTER_HANDLE th_vernier, th_hover, th_rcs[14], th_group[4];
  THRUSTER_HANDLE th_vernier, th_hover, th_rcs[14], th_group[4];
 
</nowiki></pre>
 
  
 
Change it to  
 
Change it to  
<pre><nowiki>
+
 
void Surveyor::clbkSetClassCaps (FILEHANDLE cfg)
+
void Surveyor::clbkSetClassCaps (FILEHANDLE cfg)
{
+
{
  THRUSTER_HANDLE th_vernier[3], th_hover, th_rcs[14], th_group[4];
+
  THRUSTER_HANDLE th_vernier[3], th_hover, th_rcs[14], th_group[4];
</nowiki></pre>
 
  
 
since there are 3 vernier engines, and we need to keep track of them all.
 
since there are 3 vernier engines, and we need to keep track of them all.
Line 126: Line 144:
 
It is best to put as many vessel parameters into constants as possible, to keep them all in one place and easy to adjust. The parameters for the verniers are Maximum thrust, Isp (Fuel efficiency), and engine location. So, add the following constants near the top of the file.
 
It is best to put as many vessel parameters into constants as possible, to keep them all in one place and easy to adjust. The parameters for the verniers are Maximum thrust, Isp (Fuel efficiency), and engine location. So, add the following constants near the top of the file.
  
<pre><nowiki>
+
const double VERNIER_PROP_MASS = 70.98;
const double VERNIER_PROP_MASS = 70.98;
+
const double VERNIER_ISP = 3200;
const double VERNIER_ISP = 3200;
+
const double VERNIER_THRUST = 463;
const double VERNIER_THRUST = 463;
+
const double VERNIER_RAD = 0.86;
const double VERNIER_RAD = 0.86;
+
const double VERNIER_STA = -0.5;
const double VERNIER_STA = -0.5;
 
</nowiki></pre>
 
  
 
Find the line in Surveyor::clbkSetClassCaps which reads as follows:
 
Find the line in Surveyor::clbkSetClassCaps which reads as follows:
  
<pre><nowiki>
+
  th_vernier = CreateThruster (_V(0,0,-4.35), _V(0,0,1), PB_MAXMAINTH, ph_vernier, PB_ISP);
  th_vernier = CreateThruster (_V(0,0,-4.35), _V(0,0,1), PB_MAXMAINTH, ph_vernier, PB_ISP);
+
  CreateThrusterGroup (&th_vernier, 1, THGROUP_MAIN);
  CreateThrusterGroup (&th_vernier, 1, THGROUP_MAIN);
+
  AddExhaust (th_vernier, 8, 1, _V(0,0.3,-4.35), _V(0,0,-1));
  AddExhaust (th_vernier, 8, 1, _V(0,0.3,-4.35), _V(0,0,-1));
 
</nowiki></pre>
 
  
 
Change them to:
 
Change them to:
  
<pre><nowiki>
+
  th_vernier[0] = CreateThruster(_V(        0.0*VERNIER_RAD, 1.0*VERNIER_RAD,VERNIER_STA), _V(0,0,1), VERNIER_THRUST, ph_vernier, VERNIER_ISP);
  th_vernier[0] = CreateThruster(_V(        0.0*VERNIER_RAD, 1.0*VERNIER_RAD,VERNIER_STA), _V(0,0,1), VERNIER_THRUST, ph_vernier, VERNIER_ISP);
+
  th_vernier[1] = CreateThruster(_V( sqrt(3.0)/2*VERNIER_RAD,-0.5*VERNIER_RAD,VERNIER_STA), _V(0,0,1), VERNIER_THRUST, ph_vernier, VERNIER_ISP);
  th_vernier[1] = CreateThruster(_V( sqrt(3.0)/2*VERNIER_RAD,-0.5*VERNIER_RAD,VERNIER_STA), _V(0,0,1), VERNIER_THRUST, ph_vernier, VERNIER_ISP);
+
  th_vernier[2] = CreateThruster(_V(-sqrt(3.0)/2*VERNIER_RAD,-0.5*VERNIER_RAD,VERNIER_STA), _V(0,0,1), VERNIER_THRUST, ph_vernier, VERNIER_ISP);
  th_vernier[2] = CreateThruster(_V(-sqrt(3.0)/2*VERNIER_RAD,-0.5*VERNIER_RAD,VERNIER_STA), _V(0,0,1), VERNIER_THRUST, ph_vernier, VERNIER_ISP);
+
  CreateThrusterGroup(th_vernier, 3, THGROUP_MAIN);
  CreateThrusterGroup(th_vernier, 3, THGROUP_MAIN);
+
  for(int i=0;i<3;i++) {
  for(int i=0;i<3;i++) {
+
    AddExhaust(th_vernier[i], 1, 0.1);
    AddExhaust(th_vernier[i], 1, 0.1);
+
  }
  }
 
</nowiki></pre>
 
  
 
This creates the three vernier engines, arranges them in an equilateral triangle, groups them into the "main" thruster group, and adds an exhaust display. The vernier engine geometry is adjustable with the constants VERNIER_RAD and VERNIER_STA. VERNIER_RAD determines how far away from the Z axis the thrusters are, and VERNIER_STA determines how far in front of the Center of Mass they are. In this case, VERNIER_STA is negative, so the vernier engines are behind the center of mass.  
 
This creates the three vernier engines, arranges them in an equilateral triangle, groups them into the "main" thruster group, and adds an exhaust display. The vernier engine geometry is adjustable with the constants VERNIER_RAD and VERNIER_STA. VERNIER_RAD determines how far away from the Z axis the thrusters are, and VERNIER_STA determines how far in front of the Center of Mass they are. In this case, VERNIER_STA is negative, so the vernier engines are behind the center of mass.  
Line 176: Line 188:
 
Compile this and test it. Burn the main engines and look outside. You should see three engines pointing backwards (in the direction of the legs) firing. Notice that they are extremely weak and inefficient compared to the old main engine. You probably cannot launch this from the surface anymore, so it's a good thing we put it into lunar orbit first. Don't worry yet that the engines are not aligned with the mesh.
 
Compile this and test it. Burn the main engines and look outside. You should see three engines pointing backwards (in the direction of the legs) firing. Notice that they are extremely weak and inefficient compared to the old main engine. You probably cannot launch this from the surface anymore, so it's a good thing we put it into lunar orbit first. Don't worry yet that the engines are not aligned with the mesh.
  
=== Differential Thrust ===
+
= Differential Thrust =
  
 
Now for the moment you've all been waiting for, and the justification for making this a custom DLL.
 
Now for the moment you've all been waiting for, and the justification for making this a custom DLL.
Line 190: Line 202:
 
So, in order to do all of this, we have to reorganize the code quite a bit. We will break the change into three independently-testable parts. First, move the following line from Surveyor::clbkSetClassCaps to the Surveyor class definition:
 
So, in order to do all of this, we have to reorganize the code quite a bit. We will break the change into three independently-testable parts. First, move the following line from Surveyor::clbkSetClassCaps to the Surveyor class definition:
  
<pre><nowiki>
+
  THRUSTER_HANDLE th_vernier[3], th_hover, th_rcs[14], th_group[4];
  THRUSTER_HANDLE th_vernier[3], th_hover, th_rcs[14], th_group[4];
 
</nowiki></pre>
 
  
 
This changes these variables from local variables to class fields, so they can be accessed by other class member functions. Compile and test this. It should still work identically.
 
This changes these variables from local variables to class fields, so they can be accessed by other class member functions. Compile and test this. It should still work identically.
Line 200: Line 210:
 
Add the following line to the class definition:
 
Add the following line to the class definition:
  
<pre><nowiki>
+
  void clbkPreStep(double SimT, double SimDT, double MJD);
  void clbkPreStep(double SimT, double SimDT, double MJD);
 
</nowiki></pre>
 
  
 
Add the following code somewhere near the bottom to give this method a stub.
 
Add the following code somewhere near the bottom to give this method a stub.
  
<pre><nowiki>
+
void Surveyor::clbkPreStep(double SimT, double SimDT, double MJD) {
void Surveyor::clbkPreStep(double SimT, double SimDT, double MJD) {
+
}
}
 
</nowiki></pre>
 
  
 
Now compile and test this. It still won't do anything different, but it is significantly different under the hood now. clbkPreStep is being called for each frame of the simulation.
 
Now compile and test this. It still won't do anything different, but it is significantly different under the hood now. clbkPreStep is being called for each frame of the simulation.
Line 215: Line 221:
 
Now the actual differential thrust code. Add this to Surveyor::clbkPreStep :
 
Now the actual differential thrust code. Add this to Surveyor::clbkPreStep :
  
<pre><nowiki>
+
  double P,Y,R;
  double P,Y,R;
+
  P=GetThrusterGroupLevel(THGROUP_ATT_PITCHUP)-GetThrusterGroupLevel(THGROUP_ATT_PITCHDOWN);
  P=GetThrusterGroupLevel(THGROUP_ATT_PITCHUP)-GetThrusterGroupLevel(THGROUP_ATT_PITCHDOWN);
+
  Y=GetThrusterGroupLevel(THGROUP_ATT_YAWRIGHT)-GetThrusterGroupLevel(THGROUP_ATT_YAWLEFT);
  Y=GetThrusterGroupLevel(THGROUP_ATT_YAWRIGHT)-GetThrusterGroupLevel(THGROUP_ATT_YAWLEFT);
+
  R=GetThrusterGroupLevel(THGROUP_ATT_BANKRIGHT)-GetThrusterGroupLevel(THGROUP_ATT_BANKLEFT);
  R=GetThrusterGroupLevel(THGROUP_ATT_BANKRIGHT)-GetThrusterGroupLevel(THGROUP_ATT_BANKLEFT);
+
  sprintf(oapiDebugString(),"Pitch %f Yaw %f Roll %f",P,Y,R);
  sprintf(oapiDebugString(),"Pitch %f Yaw %f Roll %f",P,Y,R);
 
  
  SetThrusterDir(th_vernier[0],_V(0.087*R,0,1));
+
  SetThrusterDir(th_vernier[0],_V(0.087*R,0,1));
  SetThrusterDir(th_vernier[1],_V(0,0,1.0+0.05*(P-Y)));
+
  SetThrusterDir(th_vernier[1],_V(0,0,1.0+0.05*(P-Y)));
  SetThrusterDir(th_vernier[2],_V(0,0,1.0+0.05*(P+Y)));
+
  SetThrusterDir(th_vernier[2],_V(0,0,1.0+0.05*(P+Y)));
</nowiki></pre>
 
  
 
Here we are using the GetThrusterGroupLevel functions to get the level setting of each of the six RCS groups. The subtraction makes us treat a turn one way as just the negative of turning the other way.  
 
Here we are using the GetThrusterGroupLevel functions to get the level setting of each of the six RCS groups. The subtraction makes us treat a turn one way as just the negative of turning the other way.  
Line 237: Line 241:
 
Compile and test this. When the main engines are on, you should see them react to rotation commands. Pitch, and both the bottom engines will get hotter or cooler. Yaw, and one bottom engine will get hotter and the other colder. Roll, and the top engine will twitch from side to side.
 
Compile and test this. When the main engines are on, you should see them react to rotation commands. Pitch, and both the bottom engines will get hotter or cooler. Yaw, and one bottom engine will get hotter and the other colder. Roll, and the top engine will twitch from side to side.
  
=== Take out the Trash ===
+
We now have a spacecraft which is maneuverable just on the thrust of its main engines. This ends part one.
 
 
Now it's time to clear out all the old ShuttlePB things that we don't need any more. Remember, if you are making something other than Surveyor, you may want to adapt this stuff rather than trashing it.
 
 
 
==== Aerodynamic Model ====
 
 
 
Surveyor is a pure spacecraft, so it doesn't need an aerodynamic model. I suppose the purists out there would insist on at least giving it drag in case it fell into the Earth's atmosphere after a bad launch, but I don't care about it.
 
 
 
Completely get rid of this block of code:
 
 
 
<pre><nowiki>
 
// Calculate lift coefficient [Cl] as a function of aoa (angle of attack) over -Pi ... Pi
 
// Implemented here as a piecewise linear function
 
double LiftCoeff (double aoa)
 
{
 
  const int nlift = 9;
 
  static const double AOA[nlift] = {-180*RAD,-60*RAD,-30*RAD,-1*RAD,15*RAD,20*RAD,25*RAD,60*RAD,180*RAD};
 
  static const double CL[nlift]  = {      0,      0,  -0.1,    0,  0.2,  0.25,  0.2,    0,      0};
 
  static const double SCL[nlift] = {(CL[1]-CL[0])/(AOA[1]-AOA[0]), (CL[2]-CL[1])/(AOA[2]-AOA[1]),
 
                                  (CL[3]-CL[2])/(AOA[3]-AOA[2]), (CL[4]-CL[3])/(AOA[4]-AOA[3]),
 
                    (CL[5]-CL[4])/(AOA[5]-AOA[4]), (CL[6]-CL[5])/(AOA[6]-AOA[5]),
 
                    (CL[7]-CL[6])/(AOA[7]-AOA[6]), (CL[8]-CL[7])/(AOA[8]-AOA[7])};
 
  for (int i = 0; i < nlift-1 && AOA[i+1] < aoa; i++);
 
  return CL[i] + (aoa-AOA[i])*SCL[i];
 
}
 
</nowiki></pre>
 
 
 
This code was used to provide a lift model for the ShuttlePB. It appears to make ShuttlePB a lifting body with a small amount of lift.
 
 
 
Now, go down to Surveyor::clbkSetClassCaps and remove the following code sections:
 
 
 
<pre><nowiki>
 
  SetCW (0.3, 0.3, 0.6, 0.9);
 
  SetWingAspect (0.7);
 
  SetWingEffectiveness (2.5);
 
  SetCrossSections (_V(10.5,15.0,5.8));
 
  SetRotDrag (_V(0.6,0.6,0.35));
 
  if (GetFlightModel() >= 1) {
 
    SetPitchMomentScale (1e-4);
 
    SetBankMomentScale (1e-4);
 
  }
 
</nowiki></pre>
 
 
 
<pre><nowiki>
 
  SetTrimScale (0.05);
 
</nowiki></pre>
 
 
 
<pre><nowiki>
 
  SetLiftCoeffFunc (LiftCoeff);
 
</nowiki></pre>
 
 
 
This removes the rest of the aerodynamic model. It also removes the aerodynamic trim capability. Compile and test, in case you forgot.
 
 
 
==== Hover engine ====
 
 
 
Surveyor uses its main engines to hover, and doesn't have a hover engine as such. So let's toss it. Get rid of this code:
 
 
 
<pre><nowiki>
 
  th_hover = CreateThruster (_V(0,-1.5,0), _V(0,1,0), PB_MAXHOVERTH, ph_vernier, PB_ISP);
 
  CreateThrusterGroup (&th_hover, 1, THGROUP_HOVER);
 
  AddExhaust (th_hover, 8, 1, _V(0,-1.5,1), _V(0,-1,0));
 
  AddExhaust (th_hover, 8, 1, _V(0,-1.5,-1), _V(0,-1,0));
 
</nowiki></pre>
 
 
 
Compile and test.
 
 
 
==== Fancy Exhaust ====
 
 
 
The ShuttlePB defines all sorts of fancy particle streams, which only are used in atmospheric flight. Since we are not in an atmosphere, let's ditch these. Your spacecraft may want to customize these instead. Get rid of these code blocks:
 
 
 
<pre><nowiki>
 
  // ***************** thruster definitions *******************
 
 
 
  PARTICLESTREAMSPEC contrail_main = {
 
    0, 5.0, 16, 200, 0.15, 1.0, 5, 3.0, PARTICLESTREAMSPEC::DIFFUSE,
 
    PARTICLESTREAMSPEC::LVL_PSQRT, 0, 2,
 
    PARTICLESTREAMSPEC::ATM_PLOG, 1e-4, 1
 
  };
 
  PARTICLESTREAMSPEC contrail_hover = {
 
    0, 5.0, 8, 200, 0.15, 1.0, 5, 3.0, PARTICLESTREAMSPEC::DIFFUSE,
 
    PARTICLESTREAMSPEC::LVL_PSQRT, 0, 2,
 
    PARTICLESTREAMSPEC::ATM_PLOG, 1e-4, 1
 
  };
 
  PARTICLESTREAMSPEC exhaust_main = {
 
    0, 2.0, 20, 200, 0.05, 0.1, 8, 1.0, PARTICLESTREAMSPEC::EMISSIVE,
 
    PARTICLESTREAMSPEC::LVL_SQRT, 0, 1,
 
    PARTICLESTREAMSPEC::ATM_PLOG, 1e-5, 0.1
 
  };
 
  PARTICLESTREAMSPEC exhaust_hover = {
 
    0, 2.0, 10, 200, 0.05, 0.05, 8, 1.0, PARTICLESTREAMSPEC::EMISSIVE,
 
    PARTICLESTREAMSPEC::LVL_SQRT, 0, 1,
 
    PARTICLESTREAMSPEC::ATM_PLOG, 1e-5, 0.1
 
  };
 
</nowiki></pre>
 
 
 
<pre><nowiki>
 
  AddExhaustStream (th_hover, _V(0,-3, 1), &contrail_hover);
 
  AddExhaustStream (th_hover, _V(0,-3,-1), &contrail_hover);
 
  AddExhaustStream (th_vernier, _V(0,0.3,-10), &contrail_main);
 
  AddExhaustStream (th_hover, _V(0,-2, 1), &exhaust_hover);
 
  AddExhaustStream (th_hover, _V(0,-2,-1), &exhaust_hover);
 
  AddExhaustStream (th_vernier, _V(0,0.3,-5), &exhaust_main);
 
</nowiki></pre>
 
 
 
Compile and Test
 
 
 
==== Translation RCS ====
 
 
 
Many modern and historical spacecraft have only limited translational capability. For example, the Cassini orbiter only has translation forward. Surveyor had no translation RCS at all. Instead it relies on the vernier engines. So let's ditch the translation thruster groups. We will leave the thrusters, but just remove the capability to use them as translation trhusters, while retaining them as rotation thrusters. Cut the following code chunks:
 
 
 
<pre><nowiki>
 
  th_group[0] = th_rcs[0];
 
  th_group[1] = th_rcs[4];
 
  th_group[2] = th_rcs[2];
 
  th_group[3] = th_rcs[6];
 
  CreateThrusterGroup (th_group, 4, THGROUP_ATT_UP);
 
 
 
  th_group[0] = th_rcs[1];
 
  th_group[1] = th_rcs[5];
 
  th_group[2] = th_rcs[3];
 
  th_group[3] = th_rcs[7];
 
  CreateThrusterGroup (th_group, 4, THGROUP_ATT_DOWN);
 
</nowiki></pre>
 
 
 
<pre><nowiki>
 
  th_group[0] = th_rcs[8];
 
  th_group[1] = th_rcs[10];
 
  CreateThrusterGroup (th_group, 2, THGROUP_ATT_LEFT);
 
 
 
  th_group[0] = th_rcs[9];
 
  th_group[1] = th_rcs[11];
 
  CreateThrusterGroup (th_group, 2, THGROUP_ATT_RIGHT);
 
 
 
  CreateThrusterGroup (th_rcs+12, 1, THGROUP_ATT_FORWARD);
 
  CreateThrusterGroup (th_rcs+13, 1, THGROUP_ATT_BACK);
 
</nowiki></pre>
 
 
 
Compile and test. Now when you fly, you can switch to translational engines, but they don't do anything.
 
 
 
=== Propellant Resources ===
 
 
 
ShuttlePB had only one fuel tank, filled with hyperthrustium fuel. Surveyor has three completely separate fuel systems:
 
 
 
# Vernier fuel and oxidizer
 
# RCS nitrogen
 
# Main retro solid fuel
 
 
 
Notice that even though in reality the vernier fuel needs to be kept in separate tanks (They are hypergolic), in Orbiter, they are both treated as "propellant" and are both kept in the same propellant resource. This is because each engine can only use one propellant resource at a time.
 
 
 
First, let's recycle the ShuttlePB fuel tank as the vernier propellant tanks. We already did this above when we replaced hpr with ph_vernier. We will, however, change the mass of propellant in it. Find the line which defines ph_vernier, and change it like this:
 
 
 
<pre><nowiki>
 
  PROPELLANT_HANDLE ph_vernier = CreatePropellantResource (VERNIER_PROP_MASS);
 
</nowiki></pre>
 
 
 
Next, create the RCS propellant resource. Whenever there are multiple propellant resources, the order in which they are created is important. The first one created is referenced as prop resource zero, the next one is one, and so on. This order matters because when the scenario is loaded or saved, the propellant levels in each tank are referenced by this number. So, add the following line below the line which creates the vernier prop resource:
 
 
 
<pre><nowiki>
 
  PROPELLANT_HANDLE ph_rcs    = CreatePropellantResource(RCS_PROP_MASS);
 
</nowiki></pre>
 
 
 
Add the constants which define the RCS to near the top of the file:
 
 
 
<pre><nowiki>
 
const double RCS_PROP_MASS=2;
 
const double RCS_ISP = 630.0;
 
const double RCS_THRUST = 0.25;
 
const double RCS_RAD = 1;
 
const double RCS_STA = -0.5;
 
const double RCS_SPACE = 0.1;
 
</nowiki></pre>
 
 
 
We will worry about the retro propellant when we define the retro engine.
 
 
 
In order to put fuel into the RCS fuel tank, edit the PRPLEVEL line for Surveyor in your scenario file:
 
 
 
<pre><nowiki>
 
  PRPLEVEL 0:1.000 1:1.000 2:1.000 
 
</nowiki></pre>
 
 
 
This fills resource zero, the vernier tanks, and resource one, the RCS tank. It also fills resource 2, the main retro solid fuel grain, but we haven't created this yet. No worries, since Orbiter ignores propellant resources which we don't have.
 
 
 
Compile and test.
 
 
 
=== Surveyor RCS ===
 
 
 
Surveyor carried a number of cold-gas thrusters for reaction control. These just used high-pressure nitrogen. These engines are weak and inefficient, but extremely simple and safe. No flammable fuel is needed for them, only a high-pressure gas tank.
 
 
 
It is a fundamental principle of attitude control that you need a minimum of two thrusters for each control direction. This is so that the linear forces of each thruster cancel out and only the torques remain. For a total of three axes (Pitch, yaw, roll), and two control directions for each (Plus and minus), you would seem to need 2*3*2=12 thrusters at a minimum for rotational RCS.
 
 
 
Surveyor only has six.
 
 
 
This is because the designers of Surveyor were primarily concerned with simplicity on board the spacecraft. They put on one thruster for plus roll and one for minus roll. These engines were installed up on Leg 1, pointing to +X and -X (left and right). Using one of these engines creates both a roll torque and a linear force. Burn one of these engines long enoug, and you may deflect your landing site or miss the moon completely. Back in the day, the guys on the ground calculating the mid-course correction had to take into account this unbalance of force. This kept with the philosophy of spacecraft simplicity by shifing the complexity to the big, room-sized computers on the ground. Fortunately since the RCS is weak, this force is small. Also, once the spacecraft settles into a particular attitude, one would expect to use about the same amount of + and - control to keep it stable. Thus, the linear forces mostly cancel out anyway.
 
 
 
Surveyor has two thrusters on Leg 2 and two on Leg 3. Both pairs have one member pointing at +Z (foreward) and one at -Z (back). These are used to control pitch and yaw. To yaw, one +Z engine on one leg and one -Z engine on the other leg are fired. These are balanced, resulting in a perfect yaw control system. To pitch, either both +Z or both -Z engines are fired. This generates a pitch torque, but also a linear thrust, which again must be acconted for on the ground.
 
 
 
One more thing to remember: The legs are well below the spacecraft center of mass. Because of this, the roll jets will induce a certain amount of yaw, as well as roll.
 
 
 
So, let's install these thrusters. First, get rid of the old ShuttlePB thrusters:
 
 
 
<pre><nowiki>
 
  th_rcs[ 0] = CreateThruster (_V( 1,0, 3), _V(0, 1,0), PB_MAXRCSTH, ph_vernier, PB_ISP);
 
  th_rcs[ 1] = CreateThruster (_V( 1,0, 3), _V(0,-1,0), PB_MAXRCSTH, ph_vernier, PB_ISP);
 
  th_rcs[ 2] = CreateThruster (_V(-1,0, 3), _V(0, 1,0), PB_MAXRCSTH, ph_vernier, PB_ISP);
 
  th_rcs[ 3] = CreateThruster (_V(-1,0, 3), _V(0,-1,0), PB_MAXRCSTH, ph_vernier, PB_ISP);
 
  th_rcs[ 4] = CreateThruster (_V( 1,0,-3), _V(0, 1,0), PB_MAXRCSTH, ph_vernier, PB_ISP);
 
  th_rcs[ 5] = CreateThruster (_V( 1,0,-3), _V(0,-1,0), PB_MAXRCSTH, ph_vernier, PB_ISP);
 
  th_rcs[ 6] = CreateThruster (_V(-1,0,-3), _V(0, 1,0), PB_MAXRCSTH, ph_vernier, PB_ISP);
 
  th_rcs[ 7] = CreateThruster (_V(-1,0,-3), _V(0,-1,0), PB_MAXRCSTH, ph_vernier, PB_ISP);
 
  th_rcs[ 8] = CreateThruster (_V( 1,0, 3), _V(-1,0,0), PB_MAXRCSTH, ph_vernier, PB_ISP);
 
  th_rcs[ 9] = CreateThruster (_V(-1,0, 3), _V( 1,0,0), PB_MAXRCSTH, ph_vernier, PB_ISP);
 
  th_rcs[10] = CreateThruster (_V( 1,0,-3), _V(-1,0,0), PB_MAXRCSTH, ph_vernier, PB_ISP);
 
  th_rcs[11] = CreateThruster (_V(-1,0,-3), _V( 1,0,0), PB_MAXRCSTH, ph_vernier, PB_ISP);
 
  th_rcs[12] = CreateThruster (_V( 0,0,-3), _V(0,0, 1), PB_MAXRCSTH, ph_vernier, PB_ISP);
 
  th_rcs[13] = CreateThruster (_V( 0,0, 3), _V(0,0,-1), PB_MAXRCSTH, ph_vernier, PB_ISP);
 
 
 
  th_group[0] = th_rcs[0];
 
  th_group[1] = th_rcs[2];
 
  th_group[2] = th_rcs[5];
 
  th_group[3] = th_rcs[7];
 
  CreateThrusterGroup (th_group, 4, THGROUP_ATT_PITCHUP);
 
 
 
  th_group[0] = th_rcs[1];
 
  th_group[1] = th_rcs[3];
 
  th_group[2] = th_rcs[4];
 
  th_group[3] = th_rcs[6];
 
  CreateThrusterGroup (th_group, 4, THGROUP_ATT_PITCHDOWN);
 
 
 
  th_group[0] = th_rcs[0];
 
  th_group[1] = th_rcs[4];
 
  th_group[2] = th_rcs[3];
 
  th_group[3] = th_rcs[7];
 
  CreateThrusterGroup (th_group, 4, THGROUP_ATT_BANKLEFT);
 
 
 
  th_group[0] = th_rcs[1];
 
  th_group[1] = th_rcs[5];
 
  th_group[2] = th_rcs[2];
 
  th_group[3] = th_rcs[6];
 
  CreateThrusterGroup (th_group, 4, THGROUP_ATT_BANKRIGHT);
 
 
 
  th_group[0] = th_rcs[8];
 
  th_group[1] = th_rcs[11];
 
  CreateThrusterGroup (th_group, 2, THGROUP_ATT_YAWLEFT);
 
 
 
  th_group[0] = th_rcs[9];
 
  th_group[1] = th_rcs[10];
 
  CreateThrusterGroup (th_group, 2, THGROUP_ATT_YAWRIGHT);
 
</nowiki></pre>
 
 
 
Now, add the following code in its place:
 
 
 
<pre><nowiki>
 
  //Roll (Leg1) jets
 
  th_rcs[ 0] = CreateThruster (_V(-RCS_SPACE,RCS_RAD,RCS_STA), _V( 1,0,0), RCS_THRUST, ph_rcs, RCS_ISP);
 
  th_rcs[ 1] = CreateThruster (_V( RCS_SPACE,RCS_RAD,RCS_STA), _V(-1,0,0), RCS_THRUST, ph_rcs, RCS_ISP);
 
 
 
  //Leg2 jets
 
  th_rcs[ 2] = CreateThruster (_V( sqrt(3.0)/2*RCS_RAD,-0.5*RCS_RAD,RCS_STA-RCS_SPACE), _V(0, 0, 1), RCS_THRUST, ph_rcs, RCS_ISP);
 
  th_rcs[ 3] = CreateThruster (_V( sqrt(3.0)/2*RCS_RAD,-0.5*RCS_RAD,RCS_STA+RCS_SPACE), _V(0, 0,-1), RCS_THRUST, ph_rcs, RCS_ISP);
 
 
 
  //Leg3 jets
 
  th_rcs[ 4] = CreateThruster (_V(-sqrt(3.0)/2*RCS_RAD,-0.5*RCS_RAD,RCS_STA-RCS_SPACE), _V(0, 0, 1), RCS_THRUST, ph_rcs, RCS_ISP);
 
  th_rcs[ 5] = CreateThruster (_V(-sqrt(3.0)/2*RCS_RAD,-0.5*RCS_RAD,RCS_STA+RCS_SPACE), _V(0, 0,-1), RCS_THRUST, ph_rcs, RCS_ISP);
 
 
 
  th_group[0] = th_rcs[3];
 
  th_group[1] = th_rcs[5];
 
  CreateThrusterGroup (th_group, 2, THGROUP_ATT_PITCHDOWN);
 
 
 
  th_group[0] = th_rcs[2];
 
  th_group[1] = th_rcs[4];
 
  CreateThrusterGroup (th_group, 2, THGROUP_ATT_PITCHUP);
 
 
 
  th_group[0] = th_rcs[0];
 
  CreateThrusterGroup (th_group, 1, THGROUP_ATT_BANKRIGHT);
 
 
 
  th_group[0] = th_rcs[1];
 
  CreateThrusterGroup (th_group, 1, THGROUP_ATT_BANKLEFT);
 
 
 
  th_group[0] = th_rcs[3];
 
  th_group[1] = th_rcs[4];
 
  CreateThrusterGroup (th_group, 2, THGROUP_ATT_YAWRIGHT);
 
 
 
  th_group[0] = th_rcs[2];
 
  th_group[1] = th_rcs[5];
 
  CreateThrusterGroup (th_group, 2, THGROUP_ATT_YAWLEFT);
 
</nowiki><pre>
 
 
 
This creates the six thrusters. The geometry is similar to the vernier engines. The difference is that there is also a constant RCS_SPACE, which spaces them a certain distance apart from the other thruster on the same leg. It also organizes them into control groups. With these groupings, all the rotational controls will work, and so should rotational autopilots like killrot, prograde, etc.
 
 
 
These are cold gas jets, and so are invisible, but you may want to add exhaust flames just for debugging purposes. The next, totally optional block, will add them. If you want, add the following code block after the previous one:
 
 
 
<pre><nowiki>
 
  for (int i=0;i<6;i++) {
 
    AddExhaust(th_rcs[i],0.1,0.05);
 
  }
 
</nowiki></pre>
 
  
Compile and test, and see that now the spacecraft turns '''very''' slowly. The real Surveyor had a turn rate of 0.5deg/sec, or 12 full minutes to turn a circle. It takes several seconds of thrusting to get up to even this slow rate.
+
Continue to [[Vessel Tutorial 2]]
  
If you turn the verniers on, note that the spacecraft becomes much more maneuverable. This is because the vernier engines are providing a power assist to the RCS. Also note that the rotation autopilots use the verniers just as well as you can use them manually.
+
[[Category: Articles]]
 +
[[Category:Tutorials]]
 +
[[Category:Add-on tutorials]]

Latest revision as of 12:13, 16 October 2022

This article intends to guide you through the construction of a new Vessel DLL, by using the sample code from the OrbiterSDK.

This documents my adventure transforming the stock ShuttlePB into a reasonably accurate model of the Surveyor lunar lander. There was a model on OrbitHangar, but it had wildly inaccurate mechanics, due partly to the fact that it was based on Vinka's Spacecraft.dll.

This tutorial assumes you have a working compiler which you know creates valid working DLLs from the OrbiterSDK sample code. I originally used the free Microsoft command-line compiler VC++ 2003, together with an editor called TextPad. I now use the free Microsoft IDE VC++ 2005. Setting this up is beyond the scope of this document. Check the rest of this Wiki, or the Orbiter forums.

The Goal Spacecraft: Surveyor[edit]

Surveyor was a series of unmanned lunar landers launched by NASA in 1965-1967, before the first Apollo. It is a particularly simple, yet interesting and highly successful design.

Functionally, Surveyor consists of a large solid-fueled main retro-rocket, three medium-sized throttleable liquid fueled vernier engines, and six cold-gas Reaction Control System (RCS) attitude control thrusters.

The Surveyor flight plan in its final minutes is one exciting ride, and obviously not good for humans. There is no possiblity of abort (two of the seven Surveyors, 2 and 4, crashed). The maximum acceleration is 9G's. And, it is very difficult to fly manually. It's a perfect job for a machine.

Surveyor is launched on top of an Atlas-Centaur rocket, on a direct impact trajectory to the moon. Impact will occur after about 66 hours. About 16 hours after launch, it performs a single mid-course correction maneuver, which aims it to precisely impact at the desired landing point.

Thirty minutes before impact, Surveyor maneuvers into a retrograde attitude. From here, its Altitude Marking Radar (AMR) will eventually be able to see the surface and measure the distance. The AMR locks on to the surface at about 200km, a little more than one minute before impact. Once the altitude reaches a pre-determined value called the altitude mark, it starts a short timer.

At the altitude mark, the spacecraft is hurtling towards the ground at over 2.5km/s and will impact in less than one minute. For Surveyor 1, the altitude mark was about 110km, and the timer was about 7 seconds. Once the timer expires, the vernier engines light up, then one second later the main retro ignites for its 40 second burn. The main retro gets rid of 2.3km/s of speed, and leaves the spacecraft between 5km and 20km above the surface and travelling downwards at between zero and 230m/s. During this stage, the vernier engines are used to stabilize the spacecraft and compensate for any thrust misalignment in the main retro. The AMR is automatically jettisoned at main retro ignition, as it is no longer needed. In fact, it is stuffed into the nozzle of the main retro, and is blasted out by the retro ignition.

The spacecraft then drops the spent retro motor and continues down on the vernier engines. Using a separate four-beam Doppler radar and a simple analog computer, it throttles the engines such that it will reach a downward velocity of about 1.3m/s about 4m above the surface. It then cuts its engines and falls the rest of the way.

Why a custom DLL[edit]

Many spacecraft can and in fact have been simulated with Vinka's spacecraft.dll. This method uses a text file to describe a spacecraft, and a general purpose DLL to interpret it.

The reason that this spacecraft must be written from scratch, and not use something like spacecraft.dll, is that when the vernier engines are on, they are used to control attitude as well as provide thrust. Two of the three engines use differential thrust to control pitch and yaw, and the third is mounted on a pivot to control roll. This effect cannot be done with spacecraft.dll, and therefore demands a custom DLL. Besides, I felt like writing a DLL, just to learn how to do it.

I hope that this article is useful to anyone trying to write a custom vessel DLL. I find it much easier to understand a concept when 1) I need to use it and 2) There is a well-documented example of using the concept. You have to provide the need. I provide the example.

The Rules[edit]

  1. ALWAYS test each change, no matter how small
  2. For historical or real vessels, get good reference material
  3. NEVER proceed to the next change if the last one doesn't work yet
  4. Start with something you know works
  5. ALWAYS ALWAYS test each change, no matter how small
  6. There is NO rule 6
  7. NEVER NEVER proceed to the next change if the last one doesn't work yet
  8. Only make one change at a time
  9. ALWAYS ALWAYS ALWAYS test each change, no matter how small
  10. NEVER NEVER NEVER proceed to the next change if the last one doesn't work yet

Historical Spacecraft Documentation[edit]

The best place to get references for NASA spacecraft is the NASA Technical Reports Server. For this vessel, applicable reports are things such as:

The Starting Point[edit]

So, how do you start with something that works, if you are creating a brand new vessel? Good question. The answer is to look through the samples in Orbiter\OrbiterSDK\Samples for the ship which is closest to the one you have in mind.

Sometimes nothing is particularly close. It was in my case. In this case, just start with the simplest possible ship, the ShuttlePB. Make a copy of this folder, then rename it to your new vessel name. If you are using Visual C++, you probably need to do some weird stuff to rename the project and all the files in it. I use the free compiler, so the project files are no good to me. If you use it also, feel free to just delete all the files except for ShuttlePB.cpp, and rename it to a new name, like Surveyor.cpp.

Also, copy the source spacecraft's config file. The config file for Surveyor is shown here, and includes an attachment point, for use for attachment to a future launch vehicle.

Classname=Surveyor
Module=Surveyor

; === Attachment specs ===
BEGIN_ATTACHMENT
P 0 0 0  0 -1 0  0 0 1  XS
END_ATTACHMENT

Now, edit Surveyor.cpp. Do a global search-and-replace to change all references from ShuttlePB to Surveyor. Go ahead and modify the copyright message at the top, to say that this is a work derived from the original ShuttlePB. By the time we are done, almost all the old ShuttlePB code will be replaced.

In keeping with Rules 1, 3, 5, 7, 9, and 10, compile this file, copy the resulting DLL to the Orbiter\Modules, and use the following scenario. It should run perfectly, and look and fly exactly like the old ShuttlePB.

BEGIN_ENVIRONMENT
  System Sol
  Date MJD 51982.6685767396
END_ENVIRONMENT

BEGIN_FOCUS
  Ship Surveyor1
END_FOCUS

BEGIN_CAMERA
  TARGET Surveyor1
  FOV 50.00
END_CAMERA

BEGIN_SHIPS
Surveyor1:Surveyor
  STATUS Landed Moon
  BASE Brighton Beach:1
  HEADING 90.00
  PRPLEVEL 0:1.000
END
END_SHIPS

If, at any point in this tutorial, it doesn't work, track down and try to identify what could have gone wrong. Since the most common symptom of failure is a Crash to Desktop (CTD) you won't have much help debugging, so keeping the changes small from version to version will help out a lot.

Meshes[edit]

Creating meshes is beyond the scope of this document. Since I am in a hurry and not primarily interested in artwork, I borrowed the mesh from Surveyor1-1.zip [1] by Jim Williams, posted on OrbitHangar.

If you are following along, get that add-on, and get the meshes out of it. Rename Surveyor-deployed.msh as Surveyor-Lander.msh. Copy this mesh to Orbiter\Meshes, and copy all the .dds files to Orbiter\Textures.

Now, to use this mesh. In Surveyor.cpp, look for the method Surveyor::clbkSetClassCaps. Near the end, change the line (Note that it doesn't say ShuttlePB, remember that you searched-and-replaced that out)

 // visual specs
 AddMesh ("Surveyor");

to

 // visual specs
 AddMesh("Surveyor-Lander");

Compile and test it. Notice that the spacecraft now looks like Surveyor, but it flies like ShuttlePB. Also note that the legs are not on the surface. Go ahead and take Surveyor for a spin, and put it in orbit around the moon. It will be much easier to test the vernier and RCS engines in space. Do a QuickSave, rename the QuickSave to something sensible like Surveyor in Orbit.scn and use the new scenario from now on.

Vehicle coordinate system[edit]

As described in the Orbiter API guide, vessels use a left-handed coordinate system. Even though Surveyor looks nothing like and is flown nothing like an airplane, an airplane is still a useful model to describe the coordinate system. Imagine yourself sitting in the pilot's seat of an airplane. The X axis is the "left-to-right" axis. +X points out to the right wing, and -X points out to the left wing. The Y axis is the "up-to-down" axis. +Y points up, in the direction of the vertical tail fin, and -Y points down in the direction of the wheels. The Z axis is the "front-to-back" axis. +Z points to the nose, and -Z points back to the aft end of the plane. In an conventional airplane or rocket, the engine's thrust vector points towards +Z. The exhaust streams out the back towards -Z. We are going to use this coordinate system, also, as it almost matches the historical Surveyor. On it, +Z and -Z are reversed and the whole thing is a right-handed system.

So, imagine yourself again sitting in the pilot seat of Surveyor. The engines and legs are behind you, on the back of your seat, and the antenna and solar panel stick out ahead of you. When you are on approach to landing, you will have your back to the surface of the Moon and your face straight up towards the stars.

Vernier Engines[edit]

These will be the "main" engines controllable with the NumPad+ key. Even though these vernier engines are the very reason we are using a custom DLL rather than spacecraft.dll, we are not going to do anything fancy to them yet.

The three vernier engines are arranged symetrically in a triangle around the Z axis. Engine 1 is near leg 1, on the +Y axis. Engines 2 and 3 are out on legs 2 and 3. They are each mounted 0.86 meters from the Z axis, 0.5m behind the origin.

First, search-and-replace variable name th_main to th_vernier, and hpr to ph_vernier. This re-names the variables for the main engines and main fuel tank.

Find this code in Surveyor::clbkSetClassCaps .

void Surveyor::clbkSetClassCaps (FILEHANDLE cfg)
{
  THRUSTER_HANDLE th_vernier, th_hover, th_rcs[14], th_group[4];

Change it to

void Surveyor::clbkSetClassCaps (FILEHANDLE cfg)
{
  THRUSTER_HANDLE th_vernier[3], th_hover, th_rcs[14], th_group[4];

since there are 3 vernier engines, and we need to keep track of them all.

It is best to put as many vessel parameters into constants as possible, to keep them all in one place and easy to adjust. The parameters for the verniers are Maximum thrust, Isp (Fuel efficiency), and engine location. So, add the following constants near the top of the file.

const double VERNIER_PROP_MASS = 70.98;
const double VERNIER_ISP = 3200;
const double VERNIER_THRUST = 463;
const double VERNIER_RAD = 0.86;
const double VERNIER_STA = -0.5;

Find the line in Surveyor::clbkSetClassCaps which reads as follows:

  th_vernier = CreateThruster (_V(0,0,-4.35), _V(0,0,1), PB_MAXMAINTH, ph_vernier, PB_ISP);
  CreateThrusterGroup (&th_vernier, 1, THGROUP_MAIN);
  AddExhaust (th_vernier, 8, 1, _V(0,0.3,-4.35), _V(0,0,-1));

Change them to:

  th_vernier[0] = CreateThruster(_V(         0.0*VERNIER_RAD, 1.0*VERNIER_RAD,VERNIER_STA), _V(0,0,1), VERNIER_THRUST, ph_vernier, VERNIER_ISP);
  th_vernier[1] = CreateThruster(_V( sqrt(3.0)/2*VERNIER_RAD,-0.5*VERNIER_RAD,VERNIER_STA), _V(0,0,1), VERNIER_THRUST, ph_vernier, VERNIER_ISP);
  th_vernier[2] = CreateThruster(_V(-sqrt(3.0)/2*VERNIER_RAD,-0.5*VERNIER_RAD,VERNIER_STA), _V(0,0,1), VERNIER_THRUST, ph_vernier, VERNIER_ISP);
  CreateThrusterGroup(th_vernier, 3, THGROUP_MAIN);
  for(int i=0;i<3;i++) {
    AddExhaust(th_vernier[i], 1, 0.1);
  }

This creates the three vernier engines, arranges them in an equilateral triangle, groups them into the "main" thruster group, and adds an exhaust display. The vernier engine geometry is adjustable with the constants VERNIER_RAD and VERNIER_STA. VERNIER_RAD determines how far away from the Z axis the thrusters are, and VERNIER_STA determines how far in front of the Center of Mass they are. In this case, VERNIER_STA is negative, so the vernier engines are behind the center of mass.

CreateThruster takes five parameters:

  1. The center of thrust in body coordinates
  2. A unit vector in the direction of thrust, in body coordinates (Note that the exhaust travels in the opposite direction)
  3. The maximum thrust of the engine, in Newtons
  4. The propellant resource (Fuel tank) this engine draws from
  5. The Isp (Fuel efficiency) of the thruster, in N*s/kg. In other words, how many Newtons does burning 1kg/s of propellant generate?

It returns a handle to the engine. This handle is a meaningless number to the vessel, but identifies the engine to Orbiter.

AddExhaust has a simple form which takes three parameters:

  1. The handle of the engine to attach to
  2. The length of the exhaust flame
  3. The radius of the flame

It automatically points in the direction the engine points (Actually since this is exhaust, it points the opposite direction) and is kept in sync with the engine direction if this changes, as it will below when we add differential thrust.

Compile this and test it. Burn the main engines and look outside. You should see three engines pointing backwards (in the direction of the legs) firing. Notice that they are extremely weak and inefficient compared to the old main engine. You probably cannot launch this from the surface anymore, so it's a good thing we put it into lunar orbit first. Don't worry yet that the engines are not aligned with the mesh.

Differential Thrust[edit]

Now for the moment you've all been waiting for, and the justification for making this a custom DLL.

During the mid-course correction and vernier descent, the three engines work together to control pitch, roll, and yaw.

Notice that there are two engines below the X axis, and one twice as far above it. Engine 1 balances the torque around X generated by both engines 2 and 3, when they are all at the same thrust. So, in order to pitch, we just burn engines 2 or 3 a little bit more or less than engine 1. Burning them more generates a nose-down pitch, and vice versa.

Yaw control involves differential thrusting on both engines 2 and 3. To yaw one way, Engine 2 thrust is increased and Engine 3 thrust is decreased the same amount. Because of where Engine 2 is mounted, it generates both pitch and yaw torque all by itself. However, with this differential thrusting, the other engine generates an equal yaw torque, but opposite pitch torque. Therefore, this differential thrusting generates a pure yaw torque.

For roll control, Engine 1 is mounted on a pivot. It pivots around the Y axis, up to 5 degrees in either direction.

So, in order to do all of this, we have to reorganize the code quite a bit. We will break the change into three independently-testable parts. First, move the following line from Surveyor::clbkSetClassCaps to the Surveyor class definition:

  THRUSTER_HANDLE th_vernier[3], th_hover, th_rcs[14], th_group[4];

This changes these variables from local variables to class fields, so they can be accessed by other class member functions. Compile and test this. It should still work identically.

Now we are going to add a brand new method to the class, clbkPreStep. Orbiter call this function each time step, after it has figured out the time step's length, but before it does the physics. This is an ideal place to put the differential thrust logic. clbkPreStep is passed the current sim time (The number in Sim 17s in the upper right corner, but with milisecond precision), the length of the next time step, and the current MJD. We don't need any of these for Surveyor, but they are available for a more complicated vessel.

Add the following line to the class definition:

  void clbkPreStep(double SimT, double SimDT, double MJD);

Add the following code somewhere near the bottom to give this method a stub.

void Surveyor::clbkPreStep(double SimT, double SimDT, double MJD) {
}

Now compile and test this. It still won't do anything different, but it is significantly different under the hood now. clbkPreStep is being called for each frame of the simulation.

Now the actual differential thrust code. Add this to Surveyor::clbkPreStep :

  double P,Y,R;
  P=GetThrusterGroupLevel(THGROUP_ATT_PITCHUP)-GetThrusterGroupLevel(THGROUP_ATT_PITCHDOWN);
  Y=GetThrusterGroupLevel(THGROUP_ATT_YAWRIGHT)-GetThrusterGroupLevel(THGROUP_ATT_YAWLEFT);
  R=GetThrusterGroupLevel(THGROUP_ATT_BANKRIGHT)-GetThrusterGroupLevel(THGROUP_ATT_BANKLEFT);
  sprintf(oapiDebugString(),"Pitch %f Yaw %f Roll %f",P,Y,R);
  SetThrusterDir(th_vernier[0],_V(0.087*R,0,1));
  SetThrusterDir(th_vernier[1],_V(0,0,1.0+0.05*(P-Y)));
  SetThrusterDir(th_vernier[2],_V(0,0,1.0+0.05*(P+Y)));

Here we are using the GetThrusterGroupLevel functions to get the level setting of each of the six RCS groups. The subtraction makes us treat a turn one way as just the negative of turning the other way.

oapiDebugString is a very useful debugging tool, in fact one of the only ones we have. This provides a pointer to a string deep inside Orbiter which it prints in black and white at the bottom left corner every time step. In this case it will print a number between -1 and 1 for each of the three axes.

SetThrusterDir is kind of a cheat here. This sets the thrust direction for an engine, and should be passed a unit-length vector. However, nothing inside of Orbiter normalizes the vector, so if you set it for example to a length 2 vector, then fly the spacecraft and use that engine at full thrust, it will generate twice its rated thrust. I haven't looked closely enough to know if it uses twice as much fuel as well.

So, we are going to manipulate the thrust vector. For engine 1 (th_vernier[0]) we are just adding an X component. We scale it by 0.087=tan(5deg) to simulate the 5deg rotation range of the engine. For engines 2 and 3, we add a factor to the Z component. Since the P term is positive for both engines, it will run them in parallel for pitch commands. Since the Y term is positive in one and negative in the other, it will run them in opposition for yaw commands.

Compile and test this. When the main engines are on, you should see them react to rotation commands. Pitch, and both the bottom engines will get hotter or cooler. Yaw, and one bottom engine will get hotter and the other colder. Roll, and the top engine will twitch from side to side.

We now have a spacecraft which is maneuverable just on the thrust of its main engines. This ends part one.

Continue to Vessel Tutorial 2