Coding a Lunar Lander from the ground up: Custom Vessel states and Staging

Hlynkacg

Aspiring rocket scientist
Addon Developer
Tutorial Publisher
Donator
Joined
Dec 27, 2010
Messages
1,874
Reaction score
0
Points
0
Location
San Diego
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...

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?
 
Top