So now that we've got the basics of vessel states down it's time to nodel the actual staging.
But before we do, lets take a look at what exactly I've put in my vessel states...
NOTE: Orbiter assumes that a vessel's main axis of thrust will be along the z+ "horizontal/fwd" axis but ours is the y+ "vertical" axis. As such I have assigned the main (ascent and descent stage) engines to "THGROUP_HOVER" rather than "THGROUP_MAIN" to prevent awkward conflicts with certain MFDs and within the Orbiter core itself.
Now that we're all on the same page...
PART 10: Staging
Staging is a complex and involved process. As such it's something that should really be broken off into it's own function. Go to your class interface and declare a new custom function if you haven't done so already.
and define it as a stub in our source file...
Finally, we'll need a way to call the function (actually jettison the stage) so go to "clbkConsumeBufferedKey" and add the following lines...
Our stage seperation function will now be activated by pressing alt + J. Now the the ground-work is out of the way let's look at the stage seperation function itself.
I learned most of my orbiter-coding-fu by studying/tearing apart the stock Atlantis' code function by function, line by line, and it just so happens that there is a function in Atlantis.cpp that does almost exactly what we want our stage seperation function to do "Atlantis::SeparateTank". We wont be copying it directly but we will be using it as a reference.
Essentially our stage seperation function needs to do three things...
Lets start by declaring some variables, I've also included a "Sanity check" to prevent the jettison routine from running if there is no descent stage to jettison...
"VESSELSTATUS" is a data structure used by orbiter to keep track of a vessel's state (velocity vectors, orientation, etc...) within the simulation.
"name" is a 256 space character string that we will use to generate a vessel name for our descent stage.
The nature of the last three variables should be readily apperant.
With our variables declared lets take a page from "Atlantis::SeparateTank" and start by spawning the new descent stage vessel. To do this we must first determine the new vessel's position, velocity and orientation within the simulation. IE the contents of it "VESSELSTATUS" data structure.
Because it is part of our parent vessel (the LM) that is being jettisoned we will simply copy our parent vessel's VESSELSTATUS and apply some transformations.
"rofs" is a placeholder variable that we will be used in calculating our translations.
"GetStatus (vs)" copies our vessel's current state to "vs" the VESSELSTATUS structure that we declared at the begining of the function.
"Local2Rel (sofs, vs.rpos);" Is an orbiter core function. It takes our seperation offset variable and writes it to the "relative position" variable inside "vs" after performing "...a transformation from local vessel coordinates to the ecliptic frame centered at the vessel's reference body."
"GlobalRot (sdir, rofs);" converts our seperation direction from our vessel's local reference frame to Orbiter's global reference frame, and saves it to rofs.
Finally "vs.rvel += rofs*svel;" takes the velocity component of "vs" and adds our seperation velocity to it along the vector rofs.
The total result is a vessel state that matches our parent vessel's but is offset by "sofs" and moving away at a rate of "svel" meters per second in "sdir" direction.
Now that we have a VESSELSTATE let's create a vessel.
step one is to get a name for our new vessel.
"strcpy (name, GetName());" copies our parent vessel's name to the "name" character string that we declared at the beginning of the function.
"strcat (name, "-DescentStage");" tacks "-DescentStage" onto the end of whatever characters were saved to our "name" string. For instance, if our LM were named "Eagle", "name" would read "Eagle-DescentStage".
Finally "oapiCreateVessel (name, "UMMU_Apollo/LM_descentstage", vs);" creates a new vessel in orbiter using the name, config file, and VESSELSTATE passed to it. In this case I am using "LM_descentstage.cfg" saved in the "UMMU_Apollo" sub-directory of "Orbiter/Config/Vessels".
Lets compile and test...
It works, but there is a problem. Our parent vessel still has a descent stage.
Lets fix that...
These lines should be self explanetory. they delete the descent stage thrusters, propellant resources and mesh from our vessel instance and then set our VesselStatus to "ASCENT" so that the rest of our vessel's functions will recognize that the descent stage has been jettisoned.
NOTE: When deleting propellant tanks or thrusters from a vessel they should always be removed in the opposite order that they were created. Failure to do so can cause some akward errors durring playbacks or when closing and opening scenarios because Orbiter records propellant levels and throttle settings by index number rather than handle. If you delete "tank 2" before you delete "tank 4", tank 2's status will become tank 3's and so on.
Compile and test again...
Much better :thumbup:
Now this last option is entirely optional but I'm going to do it because I'm a completionist and knowing how to do it may come in handy later. Lets set the propellant levels in our spawned descent stage to those in our parent vessel's descent tanks.
For this to work we need to do it after we've spawned the descent stage vessel but before we delete the descent tanks from our parent vessel.
This is actually our first interaction with external class interfaces/instances.
"OBJHANDLE oh_descent = oapiGetObjectByName (name);" gets a handle for the descent stage vessel that we just created.
"VESSEL *ds = oapiGetVesselInterface (oh_descent);" This function creates a new class interface (it does not create a new vessel) called "ds" that points to an existing vessel (our descent stage). Using this interface we can now call functions in that vessel's code as well as our own.
NOTE: much care should be taken in which functions you call. All sort of errors/bugs can arise from calling a function that is not declared or has a different definition from the vessel you're calling it from. To play it safe, restrict these calls to default Orbiter API functions such as the above.
"ds->" indicates that we are calling the function that follows in "ds"'s code rather than our own vessel's. In this case we are getting handles for 3 of ds's propellant tanks and setting their proppellant masses to the masses in our parent vessel's descent tanks.
NOTE: this assumes that the descent stage vessel has 3 tanks. this can cause issues if does not. To head-off any such problems I will simply post the source code of my descent stage here. At this point, if you've been following along, you should know what to do with it.
Compile everything one more time and test it...
Use the scenario editor to mess with the propellant levels and make sure that they transfer properly.
So far so good, yes?
we're almost done, but there are still two things left to do...
the first is minor house keeping, if our vessel starts the scenario in ascent configuration it will still appear with the descent stage mesh, engine, and tanks. They only get deleted when the stage seperation routine runs and if we're already in ascent config that wont happen.
as such we need to go into "clbkPostCreation" and apply the changes that would normally happen durring stage seperation...
The second thing we need to do is shift our vessel's center of gravity and physical parameters.
Ordinarily we would do this as part of the stage seperation function but in this case the LM's "quirk" will bite us in the ass.
You see, changing a vessels physical parameters especially it's CoG and touchdown points while a vessel is on the ground can cause all sorts of weirdness (up to and including getting catapulted into NaN-space). Where does the LM do it's staging? On the ground of course.
As such we need a work-around, fortunatly for you I've already thought of one.
What we will do is add a set of instructions to the "ASCENT" case of our switch in "clbkPreStep" that will check to see if A) our vessel is landed and B) if our parameters have already been changed. If both come back "false" our new parameters will be applied.
To make this work we need to add a new variable to our class interface...
"bool" stands for "boolean" which is a variable or function that returns one of two possible values "true" or "false".
Let's initialise "CGshifted" in the class constructor...
And then add the required lines to our switch in clbkPreStep...
Once again, compile and test...
Staging successful! :woohoo:
This concludes Part 10 and our class on custom vessel states and staging.
What topic should I cover next?
VCs? Lights? Particle streams? UMMU Support? Sound effects?
But before we do, lets take a look at what exactly I've put in my vessel states...
Code:
// --------------------------------------------------------------
// Pre-Step processes
// --------------------------------------------------------------
void LM::clbkPreStep (double simt, double simdt, double mjd)
{
// Update Vessel State
SetEmptyMass (CalcEmptyMass ());
switch (VesselStatus)
{
// Update Vessel State
SetEmptyMass (CalcEmptyMass ());
switch (VesselStatus)
{
case LAUNCH: // If vessel is in Launch config...
// Fold and stow landing gear
GearStatus = CLOSED;
gear_proc = 0;
SetAnimation (anim_Gear, 0);
// Disable engines
SetThrusterLevel (th_descent, 0);
SetThrusterLevel (th_ascent, 0);
break;
case DESCENT: // If vessel is in Descent (normal flight) config...
SetDefaultPropellantResource (ph_descentFuel); // Set descent stage fuel tank as default propellant resource
CreateThrusterGroup (&th_descent, 1, THGROUP_HOVER); // Assign descent stage engine to THGROUP_HOVER
SetThrusterLevel (th_ascent, 0); // Disable ascent stage engine
break;
case ASCENT: // If vessel is in Asecent config...
SetDefaultPropellantResource (ph_ascentFuel); // Set ascent stage fuel tank as default propellant resource
CreateThrusterGroup (&th_ascent, 1, THGROUP_HOVER); // Assign ascent stage engine to THGROUP_HOVER
break;
} // End "switch (VesselStatus)"
// Debuggery
//sprintf (oapiDebugString(), "Vessel Status %0.0f, Empty Mass %0.3f", (float)VesselStatus, GetMass());
} // End "LM::clbkPreStep"
NOTE: Orbiter assumes that a vessel's main axis of thrust will be along the z+ "horizontal/fwd" axis but ours is the y+ "vertical" axis. As such I have assigned the main (ascent and descent stage) engines to "THGROUP_HOVER" rather than "THGROUP_MAIN" to prevent awkward conflicts with certain MFDs and within the Orbiter core itself.
Now that we're all on the same page...
PART 10: Staging
Staging is a complex and involved process. As such it's something that should really be broken off into it's own function. Go to your class interface and declare a new custom function if you haven't done so already.
Code:
...
double CalcEmptyMass (void); // Calculate current empty mass of vessel
[COLOR="Red"]void StageSeparation (void); // Jettison descent stage[/COLOR]
// Overloaded callback functions
void clbkSetClassCaps (FILEHANDLE cfg); // Set the capabilities of the vessel class
...
and define it as a stub in our source file...
Code:
} // End "LM::CalcEmptyMass"
[COLOR="red"]// --------------------------------------------------------------
// Jettison descent stage
// --------------------------------------------------------------
void LM::StageSeparation (void)
{
} // End "LM::StageSeparation"[/COLOR]
Finally, we'll need a way to call the function (actually jettison the stage) so go to "clbkConsumeBufferedKey" and add the following lines...
Code:
// Jettison descent stage when [Alt] + [J] is pressed.
if (key == OAPI_KEY_J && down && !KEYMOD_SHIFT(kstate) && !KEYMOD_CONTROL (kstate) && KEYMOD_ALT(kstate))
{
StageSeparation(); // run stage seperation routine...
return 1; // ...and return '1' to indicate that the input has been processed
}
Our stage seperation function will now be activated by pressing alt + J. Now the the ground-work is out of the way let's look at the stage seperation function itself.
I learned most of my orbiter-coding-fu by studying/tearing apart the stock Atlantis' code function by function, line by line, and it just so happens that there is a function in Atlantis.cpp that does almost exactly what we want our stage seperation function to do "Atlantis::SeparateTank". We wont be copying it directly but we will be using it as a reference.
Essentially our stage seperation function needs to do three things...
- Spawn a new "descent stage" vessel
- Match the new vessel's state to our parent vessel's
- Apply the required physical changes to our parent vessel
Lets start by declaring some variables, I've also included a "Sanity check" to prevent the jettison routine from running if there is no descent stage to jettison...
Code:
// --------------------------------------------------------------
// Jettison descent stage
// --------------------------------------------------------------
void LM::StageSeparation (void)
{
[COLOR="red"]if (VesselStatus == DESCENT) // Sanity check, is there a descent stage to jettison?
{
VESSELSTATUS vs;
char name[256];
VECTOR3 sofs = LM_DES_OFFSET; // Seperation offset
VECTOR3 sdir = { 0,-1, 0}; // Seperation direction
double svel = 0.3; // Separation velocity[/COLOR]
"VESSELSTATUS" is a data structure used by orbiter to keep track of a vessel's state (velocity vectors, orientation, etc...) within the simulation.
"name" is a 256 space character string that we will use to generate a vessel name for our descent stage.
The nature of the last three variables should be readily apperant.
With our variables declared lets take a page from "Atlantis::SeparateTank" and start by spawning the new descent stage vessel. To do this we must first determine the new vessel's position, velocity and orientation within the simulation. IE the contents of it "VESSELSTATUS" data structure.
Because it is part of our parent vessel (the LM) that is being jettisoned we will simply copy our parent vessel's VESSELSTATUS and apply some transformations.
Code:
VECTOR3 sdir = { 0,-1, 0}; // Seperation direction
double svel = 0.3; // Separation velocity
[COLOR="red"]// Get vessel status structure
VECTOR3 rofs;
GetStatus (vs);
Local2Rel (sofs, vs.rpos);
GlobalRot (sdir, rofs);
vs.rvel += rofs*svel;[/COLOR]
"rofs" is a placeholder variable that we will be used in calculating our translations.
"GetStatus (vs)" copies our vessel's current state to "vs" the VESSELSTATUS structure that we declared at the begining of the function.
"Local2Rel (sofs, vs.rpos);" Is an orbiter core function. It takes our seperation offset variable and writes it to the "relative position" variable inside "vs" after performing "...a transformation from local vessel coordinates to the ecliptic frame centered at the vessel's reference body."
"GlobalRot (sdir, rofs);" converts our seperation direction from our vessel's local reference frame to Orbiter's global reference frame, and saves it to rofs.
Finally "vs.rvel += rofs*svel;" takes the velocity component of "vs" and adds our seperation velocity to it along the vector rofs.
The total result is a vessel state that matches our parent vessel's but is offset by "sofs" and moving away at a rate of "svel" meters per second in "sdir" direction.
Now that we have a VESSELSTATE let's create a vessel.
Code:
vs.rvel += rofs*svel;
[COLOR="red"]// Create descent stage as seperate vessel
strcpy (name, GetName());
strcat (name, "-DescentStage");
oapiCreateVessel (name, "UMMU_Apollo/LM_descentstage", vs); // create descent stage vessel[/COLOR]
step one is to get a name for our new vessel.
"strcpy (name, GetName());" copies our parent vessel's name to the "name" character string that we declared at the beginning of the function.
"strcat (name, "-DescentStage");" tacks "-DescentStage" onto the end of whatever characters were saved to our "name" string. For instance, if our LM were named "Eagle", "name" would read "Eagle-DescentStage".
Finally "oapiCreateVessel (name, "UMMU_Apollo/LM_descentstage", vs);" creates a new vessel in orbiter using the name, config file, and VESSELSTATE passed to it. In this case I am using "LM_descentstage.cfg" saved in the "UMMU_Apollo" sub-directory of "Orbiter/Config/Vessels".
Lets compile and test...
It works, but there is a problem. Our parent vessel still has a descent stage.
Lets fix that...
Code:
oapiCreateVessel (name, "UMMU_Apollo/LM_descentstage", vs); // create descent stage vessel
[COLOR="red"] // Remove descent stage from vessel instance
DelThruster (th_descent); // Delete descent stage engine
DelPropellantResource (ph_descentH2O); // Delete descent stage water tank
DelPropellantResource (ph_descentO2); // Delete descent stage O2 tank
DelPropellantResource (ph_descentFuel); // Delete descent stage propellant tank
DelMesh (mesh_Descent); // Delete descent stage mesh
VesselStatus = ASCENT; // Set vessel status to ascent[/COLOR]
These lines should be self explanetory. they delete the descent stage thrusters, propellant resources and mesh from our vessel instance and then set our VesselStatus to "ASCENT" so that the rest of our vessel's functions will recognize that the descent stage has been jettisoned.
NOTE: When deleting propellant tanks or thrusters from a vessel they should always be removed in the opposite order that they were created. Failure to do so can cause some akward errors durring playbacks or when closing and opening scenarios because Orbiter records propellant levels and throttle settings by index number rather than handle. If you delete "tank 2" before you delete "tank 4", tank 2's status will become tank 3's and so on.
Compile and test again...
Much better :thumbup:
Now this last option is entirely optional but I'm going to do it because I'm a completionist and knowing how to do it may come in handy later. Lets set the propellant levels in our spawned descent stage to those in our parent vessel's descent tanks.
For this to work we need to do it after we've spawned the descent stage vessel but before we delete the descent tanks from our parent vessel.
Code:
oapiCreateVessel (name, "UMMU_Apollo/LM_descentstage", vs); // create descent stage vessel
[COLOR="red"]// Match descent stage's propellant levels to LM's descent tanks
OBJHANDLE oh_descent = oapiGetObjectByName (name); // get handle of descent stage vessel
VESSEL *ds = oapiGetVesselInterface (oh_descent); // get interface for descent stage
ds->SetPropellantMass (ds->GetPropellantHandleByIndex (0), GetPropellantMass (ph_descentFuel));
ds->SetPropellantMass (ds->GetPropellantHandleByIndex (1), GetPropellantMass (ph_descentO2));
ds->SetPropellantMass (ds->GetPropellantHandleByIndex (2), GetPropellantMass (ph_descentH2O));[/COLOR]
// Remove descent stage from vessel instance
DelThruster (th_descent); // Delete descent stage engine
This is actually our first interaction with external class interfaces/instances.
"OBJHANDLE oh_descent = oapiGetObjectByName (name);" gets a handle for the descent stage vessel that we just created.
"VESSEL *ds = oapiGetVesselInterface (oh_descent);" This function creates a new class interface (it does not create a new vessel) called "ds" that points to an existing vessel (our descent stage). Using this interface we can now call functions in that vessel's code as well as our own.
NOTE: much care should be taken in which functions you call. All sort of errors/bugs can arise from calling a function that is not declared or has a different definition from the vessel you're calling it from. To play it safe, restrict these calls to default Orbiter API functions such as the above.
"ds->" indicates that we are calling the function that follows in "ds"'s code rather than our own vessel's. In this case we are getting handles for 3 of ds's propellant tanks and setting their proppellant masses to the masses in our parent vessel's descent tanks.
NOTE: this assumes that the descent stage vessel has 3 tanks. this can cause issues if does not. To head-off any such problems I will simply post the source code of my descent stage here. At this point, if you've been following along, you should know what to do with it.
Code:
// ==============================================================
// ORBITER MODULE: LM Descent
// Part of the ORBITER SDK
// Copyright (C) 2012 Greg Hlynka
// All rights reserved
//
// LM_DescentStage.cpp
// Control module for LM Descent Stage vessel class
//
// Notes:
// ==============================================================
#include "LM.h"
// ==============================================================
// Some vessel Constants
// ==============================================================
const double LM_DES_SIZE = 4.3; // Decent Stage's mean radius [m]
const VECTOR3 LM_DES_CS = { 7.38, 7.38, 12.8}; // Decent Stage x,y,z cross sections [m^2]
const VECTOR3 LM_DES_PMI = { 1.28, 1.28, 2.06}; // Decent Stage principal moments of inertia (mass-normalised) [m^2]
const VECTOR3 LM_DES_RD = { 0.01, 0.01, 0.01}; // Decent Stage rotation drag coefficients
const VECTOR3 LM_DES_TDP[3] = {{ 0.0,-2.3, 4.4}, {-3.8,-2.3,-2.2}, { 3.8,-2.3,-2.2}}; // Decent Stage touchdown points [m]
// ==============================================================
// Descent stage class Interface
// ==============================================================
class LM_Descent: public VESSEL3 {
public:
LM_Descent (OBJHANDLE hVessel, int flightmodel);
~LM_Descent ();
// Custom vessel functions
void DefineAnimations (void); // Define mesh animations
void DefineParticleStreams (void); // Define exhaust and particle streams
void DefineSounds (void); // Define sound effects
// Overloaded callback functions
void clbkSetClassCaps (FILEHANDLE cfg); // Set the capabilities of the vessel class
void clbkLoadStateEx (FILEHANDLE scn, void *status); // Read status from scenario file
void clbkSaveState (FILEHANDLE scn); // Write status to scenario file
void clbkPostStep (double simt, double simdt, double mjd); // Manage Animations and Post-Step processes
int clbkConsumeBufferedKey (DWORD key, bool down, char *kstate); // Process keyboard inputs
// Thruster and Propellant handles
PROPELLANT_HANDLE ph_descentFuel; // Functional Propellant tanks
PROPELLANT_HANDLE ph_descentO2, ph_descentH2O; // Represenative propellant tanks (consumables)
THRUSTER_HANDLE th_descent; // Functional thrusters
THRUSTER_HANDLE th_dust, th_vent; // Represenative thrusters (particle streams)
private:
// Vessel status variables
enum doorstate {CLOSED, OPEN, CLOSING, OPENING} GearStatus;
// Animations
UINT anim_Gear;
double gear_proc;
ANIMATIONCOMPONENT_HANDLE ach_GearLeg[4], ach_GearStrut[4], ach_GearLock[4];
MGROUP_TRANSFORM *mgt_Leg[4], *mgt_Strut[4], *mgt_Downlock[4];
// Meshes
MESHHANDLE mh_descent;
};
LM_Descent::LM_Descent (OBJHANDLE hVessel, int flightmodel)
: VESSEL3 (hVessel, flightmodel)
{
DefineAnimations();
// Set initial values
gear_proc = 1.0;
GearStatus = OPEN;
// Load Mesh
mh_descent = oapiLoadMeshGlobal("UMMU_Apollo/LEM_DescentStage");
}
LM_Descent::~LM_Descent ()
{
}
// ==============================================================
// Custom vessel functions
// ==============================================================
// --------------------------------------------------------------
// Define Animations
// --------------------------------------------------------------
void LM_Descent::DefineAnimations(void)
{
// Declare mesh groups to be animated
static UINT meshgroup_Legs[4][3] = {
{DS_GRP_LandingFoot_FWD, DS_GRP_ShockStrut_FWD, DS_GRP_PrimaryStrut_FWD},
{DS_GRP_LandingFoot_AFT, DS_GRP_ShockStrut_AFT, DS_GRP_PrimaryStrut_AFT},
{DS_GRP_LandingFoot_PORT, DS_GRP_ShockStrut_PORT, DS_GRP_PrimaryStrut_PORT},
{DS_GRP_LandingFoot_STBD, DS_GRP_ShockStrut_STBD, DS_GRP_PrimaryStrut_STBD}
};
static UINT meshgroup_Struts[4] = { DS_GRP_SecondaryStruts_FWD, DS_GRP_SecondaryStruts_AFT, DS_GRP_SecondaryStruts_PORT, DS_GRP_SecondaryStruts_STBD};
static UINT meshgroup_Locks[4] = { DS_GRP_Downlock_FWD, DS_GRP_Downlock_AFT, DS_GRP_Downlock_PORT, DS_GRP_Downlock_STBD};
static UINT meshgroup_Ladder = DS_GRP_Ladder;
// Create landing gear animation
anim_Gear = CreateAnimation (1.0);
for (int i = 0; i < 4; i++)
{
// Animation components
mgt_Leg[i] = new MGROUP_ROTATE ( 0, &meshgroup_Legs[i][0], 3, LM_LegPivot[i], LM_LegAxis[i], (float) 45*RAD); // Animate landing legs
mgt_Strut[i] = new MGROUP_ROTATE ( 0, &meshgroup_Struts[i], 1, LM_StrutPivot[i], LM_LegAxis[i], (float)-63*RAD); // Animate Support Struts attatched to legs
mgt_Downlock[i] = new MGROUP_ROTATE ( 0, &meshgroup_Locks[i], 1, LM_DownlockPivot[i], LM_LegAxis[i], (float) 150*RAD); // Animate Locking mechanism joining support struts to body
// Add individual components to 'anim_Gear'
ach_GearLeg[i] = AddAnimationComponent (anim_Gear, 0, 1, mgt_Leg[i]);
ach_GearStrut[i] = AddAnimationComponent (anim_Gear, 0, 1, mgt_Strut[i], ach_GearLeg[i]);
ach_GearLock[i] = AddAnimationComponent (anim_Gear, 0, 1, mgt_Downlock[i], ach_GearStrut[i]);
}
static MGROUP_ROTATE mgt_Ladder ( 0, &meshgroup_Ladder, 1, LM_LegPivot[0], LM_LegAxis[0], (float) 45*RAD); // Apply front leg's animation state to ladder.
AddAnimationComponent (anim_Gear, 0, 1, &mgt_Ladder);
} // end "DefineAnimations(void)"
// --------------------------------------------------------------
// Define exhaust and particle streams
// --------------------------------------------------------------
void LM_Descent::DefineParticleStreams (void)
{
// Load exhaust textures
SURFHANDLE tex_main = oapiRegisterExhaustTexture ("Exhaust2");
AddExhaust (th_descent, 4.0, 0.8, 1.4, tex_main);
} // End "LM_Descent::DefineParticleStreams"
// --------------------------------------------------------------
// Define sound effects
// --------------------------------------------------------------
void LM_Descent::DefineSounds (void)
{
} // End "LM_Descent::DefineSounds"
// ==============================================================
// Overloaded callback functions
// ==============================================================
// --------------------------------------------------------------
// Set capabilities of vessel
// --------------------------------------------------------------
void LM_Descent::clbkSetClassCaps (FILEHANDLE cfg)
{
// physical vessel parameters
SetSize (LM_DES_SIZE);
SetCrossSections (LM_DES_CS);
SetPMI (LM_DES_PMI);
SetRotDrag (LM_DES_RD);
SetEmptyMass (LM_DES_EMPTYMASS);
SetTouchdownPoints (LM_DES_TDP[0], LM_DES_TDP[1], LM_DES_TDP[2]);
// propellant resources
ph_descentFuel = CreatePropellantResource (LM_DES_FUELMASS); // Descent stage propellant tank
ph_descentO2 = CreatePropellantResource (LM_DES_O2MASS); // Descent stage O2 tank
ph_descentH2O = CreatePropellantResource (LM_DES_WATERMASS); // Descent stage water tank
// main engine
th_descent = CreateThruster (LM_DES_ENGINEPOS - LM_DES_OFFSET, _V( 0, 1, 0), LM_DES_MAXTHRUST, ph_descentFuel, LM_DES_ISP);
// associate a mesh for the visual
AddMesh (mh_descent);
SetMeshVisibilityMode (0, MESHVIS_ALWAYS);
// camera parameters
SetCameraOffset (_V( 0.0, 2.4, 0.0));
}
// --------------------------------------------------------------
// Read status from scenario file
// --------------------------------------------------------------
void LM_Descent::clbkLoadStateEx (FILEHANDLE scn, void *status)
{
int i = 0;
char *cbuf;
while (oapiReadScenario_nextline (scn, cbuf))
{
// Load animation states
if (!_strnicmp (cbuf, "GEAR", 4)) // find line labeled "GEAR" in scn file
{
sscanf (cbuf+4, "%i %lf", &GearStatus, &gear_proc); // Read values stored there to GearStatus and gear_proc
SetAnimation (anim_Gear, gear_proc); // Apply process value to animation.
}
// Load default parameters
else ParseScenarioLineEx (cbuf, status);
} // End "while (oapiReadScenario_nextline (scn, cbuf))"
} // End "clbkLoadStateEx(FILEHANDLE scn, void *status)"
// --------------------------------------------------------------
// Write status to scenario file
// --------------------------------------------------------------
void LM_Descent::clbkSaveState (FILEHANDLE scn)
{
int i = 0;
char cbuf[256];
// Write default vessel parameters to file
VESSEL3::clbkSaveState (scn);
// Write custom parameters to file
sprintf (cbuf, "%i %0.4f", GearStatus, gear_proc); // Landing Gear status and animation state
oapiWriteScenario_string (scn, "GEAR", cbuf);
} // End "clbkSaveState(FILEHANDLE scn)"
// --------------------------------------------------------------
// Post-Step processes and Animations
// --------------------------------------------------------------
void LM_Descent::clbkPostStep (double simt, double simdt, double mjd)
{
// Landing Gear animation control logic
if (gear_proc < 0) // If process value is less than 0...
{
GearStatus = CLOSED; // ...set status to "CLOSED"
gear_proc = 0; // and process value to 0
}
else if (gear_proc > 1) // If process value is greater than 1...
{
GearStatus = OPEN; // ...set status to "OPEN"
gear_proc = 1; // and process value to 1
}
if (GearStatus > CLOSED)
{
double delta = simdt / 0.3; // delta = simdt divided by duration of animation in seconds
if (GearStatus == OPENING) // if Status equals "OPENING"...
{
gear_proc += delta; // ...add delta to process value
}
if (GearStatus == CLOSING) // if Status equals "CLOSING"...
{
gear_proc -= delta; // ...subtract it.
}
SetAnimation( anim_Gear, gear_proc); // apply process value to animation
}
//// Debuggery
//sprintf(oapiDebugString(), "Landing Gear Status %0.0f, gear_proc %0.3f", (float)GearStatus, gear_proc);
}
// --------------------------------------------------------------
// Process keyboard inputs
// --------------------------------------------------------------
int LM_Descent::clbkConsumeBufferedKey (DWORD key, bool down, char *kstate)
{
// Deploy Landing Gear when [G] is pressed.
if(key == OAPI_KEY_G && down && !KEYMOD_SHIFT(kstate) && !KEYMOD_CONTROL (kstate) && !KEYMOD_ALT(kstate))
{
if (GearStatus != OPEN) GearStatus = OPENING;
else GearStatus = CLOSING;
return 1;
}
return 0; // if no keys are pressed the function returns '0' and nothing happens
}
// ==============================================================
// API callback interface
// ==============================================================
// --------------------------------------------------------------
// Vessel initialisation
// --------------------------------------------------------------
DLLCLBK VESSEL *ovcInit (OBJHANDLE hvessel, int flightmodel)
{
return new LM_Descent (hvessel, flightmodel);
}
// --------------------------------------------------------------
// Vessel cleanup
// --------------------------------------------------------------
DLLCLBK void ovcExit (VESSEL *vessel)
{
if (vessel) delete (LM_Descent*)vessel;
}
Compile everything one more time and test it...
Use the scenario editor to mess with the propellant levels and make sure that they transfer properly.
So far so good, yes?
we're almost done, but there are still two things left to do...
the first is minor house keeping, if our vessel starts the scenario in ascent configuration it will still appear with the descent stage mesh, engine, and tanks. They only get deleted when the stage seperation routine runs and if we're already in ascent config that wont happen.
as such we need to go into "clbkPostCreation" and apply the changes that would normally happen durring stage seperation...
Code:
void LM::clbkPostCreation ()
{
DefineAnimations ();
DefineLighting ();
DefineParticleStreams ();
DefineSounds ();
[COLOR="red"]if (VesselStatus == ASCENT) // If vessel is spawned in ASCENT config we need to apply the changes that would normally happen durring stage seperation...
{
DelThruster (th_descent); // Delete descent stage engine
DelPropellantResource (ph_descentH2O); // Delete descent stage water tank
DelPropellantResource (ph_descentO2); // Delete descent stage O2 tank
DelPropellantResource (ph_descentFuel); // Delete descent stage propellant tank
DelMesh (mesh_Descent); // Delete descent stage mesh
}[/COLOR]
The second thing we need to do is shift our vessel's center of gravity and physical parameters.
Ordinarily we would do this as part of the stage seperation function but in this case the LM's "quirk" will bite us in the ass.
You see, changing a vessels physical parameters especially it's CoG and touchdown points while a vessel is on the ground can cause all sorts of weirdness (up to and including getting catapulted into NaN-space). Where does the LM do it's staging? On the ground of course.
As such we need a work-around, fortunatly for you I've already thought of one.
What we will do is add a set of instructions to the "ASCENT" case of our switch in "clbkPreStep" that will check to see if A) our vessel is landed and B) if our parameters have already been changed. If both come back "false" our new parameters will be applied.
To make this work we need to add a new variable to our class interface...
Code:
private:
// Vessel status variables
enum vesselstate {LAUNCH, DESCENT, ASCENT, WRECKED} VesselStatus;
enum doorstate {CLOSED, OPEN, CLOSING, OPENING} HatchStatus, GearStatus;
double thrust_mod, dv_mod, consumables_mod;
[COLOR="red"]bool CGshifted;[/COLOR]
"bool" stands for "boolean" which is a variable or function that returns one of two possible values "true" or "false".
Let's initialise "CGshifted" in the class constructor...
Code:
// Initial vessel state
VesselStatus = DESCENT;
[COLOR="red"]CGshifted = false;[/COLOR]
And then add the required lines to our switch in clbkPreStep...
Code:
case ASCENT: // If vessel is in Ascent config...
SetDefaultPropellantResource (ph_ascentFuel); // Set ascent stage fuel tank as default propellant resource
CreateThrusterGroup (&th_ascent, 1, THGROUP_HOVER); // Assign ascent stage engine to THGROUP_HOVER
[COLOR="red"]if ((CGshifted == false) && (GroundContact() == false)) // If center of gravity has not already been shifted and LM is not touching the ground...
{
SetSize (LM_ASC_SIZE); // Update size
SetPMI (LM_ASC_PMI); // Update moments of intertia
SetCrossSections (LM_ASC_CS); // Update cross sections
SetRotDrag (LM_ASC_RD); // Update rotational drag
SetTouchdownPoints (LM_ASC_TDP[0], LM_ASC_TDP[1], LM_ASC_TDP[2]); // Update touchdown points
ShiftCG (LM_ASC_OFFSET); // Shift center of gravity
CGshifted = true; // Mark "CGshifted" as "true"
}[/COLOR]
break;
} // End "switch (VesselStatus)"
Once again, compile and test...
Staging successful! :woohoo:
This concludes Part 10 and our class on custom vessel states and staging.
What topic should I cover next?
VCs? Lights? Particle streams? UMMU Support? Sound effects?