Authors Note:
I know little to nothing about c++ outside the bounds of Orbiter's API.
I'm writing this tutorial in the hopes that it will help other hobbiests, tinkerers, and budding addon developers avoid the stupid mistakes and general frustration I've subjected myself to.
Further More if you want the meshes and complete code used in this tutorial dowload my [ame="http://www.orbithangar.com/searchid.php?ID=5846"]Spider Lunar Lander *Beta v2.3*[/ame] addon.
With that out of the way, Let's Begin...
PART 1: Blank Slate, Now what?
Ok when last I left you we had a .dll based vessel that looked and behaved exactly like a Shuttle PB.
This Shuttle PB is not a Lunar Lander. Let's open our project in visual studio and get to work.
Our first step will be to break our existing code into more managable chunks. To do this start by clicking on the folder marked "Header Files" in your Solution explorer and selecting "Add Item".
*Ignore my project title, it will only give you false hope*
Select "Header" (*.h) file from the menu and give it an appropriate name. This is going to be the primary definition file for our vessel class so I'd name it after the vessel.
We now have a blank header file. As per wikipedia a header file is...
...a file that allows programmers to separate certain elements of a program's source code into reusable files. Header files commonly contain forward declarations of classes, subroutines, variables, and other identifiers. Programmers who wish to declare standardized identifiers in more than one source file can place such identifiers in a single header file, which other code can then include whenever the header contents are required. This is to keep the interface in the header separate from the implementation. The C standard library and C++ standard library traditionally declare their standard functions in header files.
Translated into Cro-Magnon what this allows us to declare any constants or variables once and then use and reuse them across multiple functions, components and even vessels simply by adding a single line to our vessel's source code.
You'll note that our vessel already has one such line that reads.
This is the file that tells Visual Studio what to do with instructions like "SetThrusterResource(x, y)" and "SetDockParams(a,b,c)" that are unique to Orbiter.
To populate our header find block of code in our source file that reads...
These two blocks are your vessel's "Constructor" and "Destructor" functions. They are called any time an instance of your vessel is added to or removed from the simulation. (You may want to label them for future reference)
...everything above them Will be going into our header file so get with the Cutting and Pasting.
Anyway our vessel's code is now split into two files. Remember to add
to the top of your source file and make sure it compiles.
It' does? Good! Moving on...
PART 2: Visuals
So we've done a bunch of work but our vessel still looks and behaves like a ShuttlePB and not a Lunar Lander or Galor-class Cardassian warship.
Now the sad truth is that most addon request are less interested in Diamond-Hard Spacecraft Simulation then they are in simply finding something that looks the part. So let's make our Shuttle PB look the part.
Go to your vessel's class interface (now in our header file) and add the following lines...
NOTE: I deleted the references to airfoils because Lunar landers don't have wings or control surfaces.
Now you may be wondering why I have declared two meshes. The answer is simple, Lunar Landers have two stages and if want those stages to seperate at some point it is much easier/more efficient to load multiple meshs and then add/remove them from the vessel as needed then it is to create a seperate mesh for each phase of flight.
These lines are not strictly necessary but they will make things easier down the line when it comes time to do things like animations or the stage seperation routine.
Anyway we now have a pair of mesh handles and indices, howver they are currently just bits of code not associated with a specific mesh (*.msh) file. Let's fix that.
Find your contructor (you remember where it is don't you?) and add the following...
"oapiLoadMeshGlobal" is an orbiter API function that loads a specific mesh file and commits it to memory, tying to the given mesh handle. The line contained by the perenthesises represents the file path within your Orbiter installation's "Meshes" directory.
E.G. oapiLoadMeshGlobal("UMMU_Apollo/LEM_AscentStage") tells Orbiter to load "LEM_AscentStage.msh" contained within the UMMU_Apollo sub directory. Your own file paths/names may (and probably will) be different.
Now that we have loaded the mesh files and assigned handles to them. We need to actually add them to our vessel.
the "clbkSetClassCaps" function sets the default capabilities and parameters of your vessel in Orbiter. This can include definition of physical properties (size, mass, docking ports, etc.), creation of propellant resources and thrusters, as well as visual properties. Find it in your source code and scroll to the bottom. (delete the airfoil definitions while we're here, we don't need them)
You'll see a line that says
See why constistant commenting/labeling is important?
Let's add our Ascent and Descent Stage meshes to the stack.
Note that by using the mesh handles rather than the file path we can load the mesh once but display it multiple times, not a big deal now but will matter down the road.
Anyway lets compile and take a look at our vessel in Orbiter.
:blink:
Something is seriously wrong. The two stages are smashed together and you can still see the nose of the ShuttlePB sticking out the front.
But don't worry I know how to fix this. The mesh coordinates are defined in respect to the individual stage's center of gravity/mass not the COG of the combined vessel. To we need to add a pair of offsets to compensate.
Lets go back to our header file and discuss constants.
As the name implies, a constant is an identifier whose associated value cannot typically be altered by the program. Although a constant's value is specified only once, it can be referenced many times throughout a program. Using a constant instead of specifying a value multiple times in the program can not only simplify code maintenance, but it can also supply a meaningful name for it and consolidate such constant bindings to a standard code location (for example, at the beginning). (Or your Header file)
Near the top of your header file you will see a list of various vessel parameters. Each one prefaced with a "const" keyword. These are our constants, lets add two more.
Note: I like to identify my constants and definitions in all caps so I remember that they are constants and don't try to modify in runtime.
Now if you look in the orbiter API documentation you will see that the AddMesh function allows pointing to a vector3 constant. So lets go back and apply our offsets. we might as well delet the orginal SuttlePB mesh while we're at it.
the "&" indicates a pointer.
Lets recompile and look again...
Much better :thumbup:
Sure, it may fly like a SuttlePB but at least it LOOKS the part.
Tune in Next episode when we'll be covering animations.
I know little to nothing about c++ outside the bounds of Orbiter's API.
I'm writing this tutorial in the hopes that it will help other hobbiests, tinkerers, and budding addon developers avoid the stupid mistakes and general frustration I've subjected myself to.
Further More if you want the meshes and complete code used in this tutorial dowload my [ame="http://www.orbithangar.com/searchid.php?ID=5846"]Spider Lunar Lander *Beta v2.3*[/ame] addon.
With that out of the way, Let's Begin...
PART 1: Blank Slate, Now what?
Ok when last I left you we had a .dll based vessel that looked and behaved exactly like a Shuttle PB.
This Shuttle PB is not a Lunar Lander. Let's open our project in visual studio and get to work.
Our first step will be to break our existing code into more managable chunks. To do this start by clicking on the folder marked "Header Files" in your Solution explorer and selecting "Add Item".
*Ignore my project title, it will only give you false hope*
Select "Header" (*.h) file from the menu and give it an appropriate name. This is going to be the primary definition file for our vessel class so I'd name it after the vessel.
We now have a blank header file. As per wikipedia a header file is...
...a file that allows programmers to separate certain elements of a program's source code into reusable files. Header files commonly contain forward declarations of classes, subroutines, variables, and other identifiers. Programmers who wish to declare standardized identifiers in more than one source file can place such identifiers in a single header file, which other code can then include whenever the header contents are required. This is to keep the interface in the header separate from the implementation. The C standard library and C++ standard library traditionally declare their standard functions in header files.
Translated into Cro-Magnon what this allows us to declare any constants or variables once and then use and reuse them across multiple functions, components and even vessels simply by adding a single line to our vessel's source code.
Code:
#include "*your file-name here*.h"
You'll note that our vessel already has one such line that reads.
Code:
#include "orbitersdk.h"
This is the file that tells Visual Studio what to do with instructions like "SetThrusterResource(x, y)" and "SetDockParams(a,b,c)" that are unique to Orbiter.
To populate our header find block of code in our source file that reads...
Code:
*your vessel's name here*::*your vessel's name here* (OBJHANDLE hVessel, int flightmodel)
: VESSEL3 (hVessel, flightmodel)
{
}
*your vessel's name here*::~*your vessel's name here* ()
{
}
These two blocks are your vessel's "Constructor" and "Destructor" functions. They are called any time an instance of your vessel is added to or removed from the simulation. (You may want to label them for future reference)

...everything above them Will be going into our header file so get with the Cutting and Pasting.
Anyway our vessel's code is now split into two files. Remember to add
Code:
#include "*your file-name here*.h"
to the top of your source file and make sure it compiles.
It' does? Good! Moving on...
PART 2: Visuals
So we've done a bunch of work but our vessel still looks and behaves like a ShuttlePB and not a Lunar Lander or Galor-class Cardassian warship.
Now the sad truth is that most addon request are less interested in Diamond-Hard Spacecraft Simulation then they are in simply finding something that looks the part. So let's make our Shuttle PB look the part.
Go to your vessel's class interface (now in our header file) and add the following lines...
Code:
// ==============================================================
// Shuttle-PB class interface
// ==============================================================
class LM: public VESSEL3 {
public:
LM (OBJHANDLE hVessel, int flightmodel);
~LM ();
// Orbiter CallBack Functions
void clbkSetClassCaps (FILEHANDLE cfg);
private:
[COLOR="Red"] MESHHANDLE mh_descent, mh_ascent; // Mesh handles
UINT mesh_Ascent; // Ascent stage mesh
UINT mesh_Descent; // Descent stage mesh[/COLOR]};
NOTE: I deleted the references to airfoils because Lunar landers don't have wings or control surfaces.
Now you may be wondering why I have declared two meshes. The answer is simple, Lunar Landers have two stages and if want those stages to seperate at some point it is much easier/more efficient to load multiple meshs and then add/remove them from the vessel as needed then it is to create a seperate mesh for each phase of flight.
These lines are not strictly necessary but they will make things easier down the line when it comes time to do things like animations or the stage seperation routine.
Anyway we now have a pair of mesh handles and indices, howver they are currently just bits of code not associated with a specific mesh (*.msh) file. Let's fix that.
Find your contructor (you remember where it is don't you?) and add the following...
Code:
LM::LM (OBJHANDLE hVessel, int flightmodel)
: VESSEL3 (hVessel, flightmodel)
{
[COLOR="red"] // Load Exterior meshes
mh_descent = oapiLoadMeshGlobal("UMMU_Apollo/LEM_DescentStage");
mh_ascent = oapiLoadMeshGlobal("UMMU_Apollo/LEM_AscentStage");[/COLOR]
}
"oapiLoadMeshGlobal" is an orbiter API function that loads a specific mesh file and commits it to memory, tying to the given mesh handle. The line contained by the perenthesises represents the file path within your Orbiter installation's "Meshes" directory.
E.G. oapiLoadMeshGlobal("UMMU_Apollo/LEM_AscentStage") tells Orbiter to load "LEM_AscentStage.msh" contained within the UMMU_Apollo sub directory. Your own file paths/names may (and probably will) be different.
Now that we have loaded the mesh files and assigned handles to them. We need to actually add them to our vessel.
the "clbkSetClassCaps" function sets the default capabilities and parameters of your vessel in Orbiter. This can include definition of physical properties (size, mass, docking ports, etc.), creation of propellant resources and thrusters, as well as visual properties. Find it in your source code and scroll to the bottom. (delete the airfoil definitions while we're here, we don't need them)
You'll see a line that says
Code:
// associate a mesh for the visual
AddMesh ("ShuttlePB");
See why constistant commenting/labeling is important?
Let's add our Ascent and Descent Stage meshes to the stack.
Code:
// associate a mesh for the visual
AddMesh ("ShuttlePB");
mesh_Descent = AddMesh(mh_descent);
mesh_Ascent = AddMesh(mh_ascent);
Note that by using the mesh handles rather than the file path we can load the mesh once but display it multiple times, not a big deal now but will matter down the road.
Anyway lets compile and take a look at our vessel in Orbiter.
:blink:
Something is seriously wrong. The two stages are smashed together and you can still see the nose of the ShuttlePB sticking out the front.
But don't worry I know how to fix this. The mesh coordinates are defined in respect to the individual stage's center of gravity/mass not the COG of the combined vessel. To we need to add a pair of offsets to compensate.
Lets go back to our header file and discuss constants.
As the name implies, a constant is an identifier whose associated value cannot typically be altered by the program. Although a constant's value is specified only once, it can be referenced many times throughout a program. Using a constant instead of specifying a value multiple times in the program can not only simplify code maintenance, but it can also supply a meaningful name for it and consolidate such constant bindings to a standard code location (for example, at the beginning). (Or your Header file)
Near the top of your header file you will see a list of various vessel parameters. Each one prefaced with a "const" keyword. These are our constants, lets add two more.
Code:
const VECTOR3 LM_ASC_OFFSET = { 0.00, 1.44, 0.00}; // Offset of Ascent stage COG from combined COG
const VECTOR3 LM_DES_OFFSET = { 0.00,-0.80, 0.00}; // Offset of Descent stage COG from combined COG
Now if you look in the orbiter API documentation you will see that the AddMesh function allows pointing to a vector3 constant. So lets go back and apply our offsets. we might as well delet the orginal SuttlePB mesh while we're at it.
Code:
// associate a mesh for the visual
mesh_Descent = AddMesh( mh_descent, &LM_DES_OFFSET);
mesh_Ascent = AddMesh( mh_ascent, &LM_ASC_OFFSET);
Lets recompile and look again...
Much better :thumbup:
Sure, it may fly like a SuttlePB but at least it LOOKS the part.
Tune in Next episode when we'll be covering animations.