Coding a Lunar Lander from the ground up: Creating a member class

Hlynkacg

Aspiring rocket scientist
Addon Developer
Tutorial Publisher
Donator
Joined
Dec 27, 2010
Messages
1,870
Reaction score
3
Points
0
Location
San Diego
I've been putting of writing this part because it's complicated and it involves a bunch of programming that does not directly apply to Orbiter. That said I really need to post something before my "in-progress point" goes too much further, so without further ado...

PART 13: Classes and Objects
In C++ a class is an expanded concept of a data structure that can hold both data and functions. An object is specific instance of a class. In terms of variables, a class would be the type, and an object would be the variable. Classes and the individual objects/instances within that class exist independently of each other but can interact via a class interface.

Every vessel that exists within an Orbiter simulation state is an instance of the Orbiter API "VESSEL" class. These instances are then assigned a sub-class that details the vessel's specific properties. If you look in our header file, at the top of our class declaration you will see these lines...

Code:
// ==============================================================
// Lunar Module class interface
// ==============================================================

class LM: public VESSEL3 
{
public:
	LM (OBJHANDLE hVessel, int flightmodel);
	~LM ();

...They declare our new "LM" class as a new sub-class/object of orbiter's VESSEL3 class. This is what tells Orbiter to treat our dll as a spcaecraft rather than a planet or MFD. It's also what allows our lunar lander to access the Orbiter API's default functions as well as have things like thrusters, propellant tanks, and animations, which are themselves extensions of the "VESSEL" parent class.

For 90% of all orbiter vessel addons this is all you really need to know.

Unfortunatly, I am not writing a 90% addon. Looking at our virtual cockpit you will see that we have a lot of switches and displays. 182 of them to be precise, and each one will need it's own sub-function if we want them to be interactive. Obviously, our vessel's class declaration would rapidly become large and unwieldy if we were to add all of these functions and their associated variables to it directly. As such I have decided to to add two member classes to our vessel. One to handle user inputs and cockpit displays, and another to handle environmental and subsystem modeling.

NOTE:
I want to make it absolutely clear that this is not required. For something less complex, like the stock Delta Glider or ShuttleA, this is overkill. If you can save yourself the trouble by all means do so.


That said, knowing how to do this may come in handy so lets get to it.

the first step is to declare a "friend class" in our vessel's class interface.

Code:
class LM: public VESSEL3 
{
public:
	LM (OBJHANDLE hVessel, int flightmodel);
	~LM ();

[COLOR="Red"]	// Member classes
	friend class	LM_COCKPIT;
	LM_COCKPIT		*vc;					// Virtual cockpit interface[/COLOR]

	// Custom vessel functions
	void	DefineAnimations (void);												// Define mesh animations

Declaring a class as a "friend" of another allows instances of those two classes to share data with each other.

Next we need to add a Header and a Source file for our new class, I called mine "LM_cockpit" .h and .cpp.

In my LM_Cockpit.h I declare our new class along with some of the basic functions I know I will be needing.

Code:
// ==============================================================
// Class declaration
// ==============================================================
class LM_COCKPIT 
{
friend class LM;

public:
	LM_COCKPIT (LM *vessel);
	~LM_COCKPIT ();

	inline const LM *GetVessel() const { return v; } // Return a vessel interface for the LM to which this cockpit belongs 

	// Public Functions
	void	InitVC (UINT mesh);										// Load VC animations, bitmaps and initial VC state
	void	RegisterActiveAreas (VECTOR3 ofs);						// Register VC active areas
	bool	MouseEvent (int id, int event, VECTOR3 &p);				// Respond to user inputs
	bool	RedrawEvent (int id, int event, SURFHANDLE surf);		// Respond to redraw requests
	bool	ParseScenarioLine (char *line);							// Read status from scenario file
	void	SaveState (FILEHANDLE scn);								// Write status to scenario file
	void	PreStep (double simt, double simdt, double mjd);		// Pre-Step processes
	void	PostStep (double simt, double simdt, double mjd);		// Post-Step processes

private:
	LM	*v;							// vessel interface

};

Next I add the constructor and destructor of my cockpit class along with stubs for all my functions to LM_Cockpit.cpp.

Code:
// ==============================================================
//                 Orbiter Module: LM_Cockpit
//    part of the Apollo Applications Project for Orbiter (AAPO)
//               copyright (c) 2013 Greg Hlynka
//                    all rights reserved
//
// LM_Cockpit.cpp
// control module for AAPO Lunar Module's virtual cockpit and displays
//
// notes: Writing tutorials is hard
// ==============================================================

#include "orbitersdk.h"
#include "LM.h"
#include "LM_Cockpit.h"

// ==============================================================
// LM Cockpit class interface
// ==============================================================

// --------------------------------------------------------------
// Constructor
// --------------------------------------------------------------
LM_COCKPIT::LM_COCKPIT (LM *vessel)
{
	v = vessel;
	int i = 0;
}

// --------------------------------------------------------------
// Destructor
// --------------------------------------------------------------
LM_COCKPIT::~LM_COCKPIT ()
{
}

// ==============================================================
// Public functions
// ==============================================================
*Function Stubs Go Here*

// ==============================================================
// Private functions
// ==============================================================
*More Stubs*

At this point you should probably stop and make sure that everything compiles.

I also want to draw attention the "LM *vessel" initializer in our class constructor, this is how we'll need this to attach the individual cockpit instance to its specific Lunar module.

Assuming everything works, it's now time to add an instance of our cockpit class to our LM. Because we want a LM Cockpit for every LM in the scenario we will add the call to create a new cockpit to our LM's constructor. This way orbiter will create a cockpit instance and corresponding interface for it each time a LM instance is created.

Code:
// --------------------------------------------------------------
// Constructor
// --------------------------------------------------------------
LM::LM (OBJHANDLE hVessel, int flightmodel)
: VESSEL3 (hVessel, flightmodel)
{
	// Config file difficulty modifiers (1.0 = 100% of default value)
	thrust_mod		= 1.0;
	dv_mod			= 1.0;
	consumables_mod = 1.0;

[COLOR="red"]	// Load sub classes
	vc = new LM_COCKPIT (this);	// Cockpit class
[/COLOR]...

"this" in the cockpit class call is refers to the specific LM instance being constructed and gets passed to the "LM *vessel" initializer I pointed out earlier. The LM and it's cockpit are now linked, and Cockpit class functions can be accessed using the "vc" interface.

Now that we have provisions for creating cockpits we need a provission for destroying one. Add the following line to our LM's class destructor.

Code:
// --------------------------------------------------------------
// Destructor
// --------------------------------------------------------------
LM::~LM ()
{
	delete	vc;
}

Otherwise if our LM were to be deleted from the simulation the cockpit class would continue to float around taking up memory and processing power. Remember kids, only you can prevent memory leaks!

Now that the ground work has been laid we can start doing things with our shiny new cockpit class. To access the cockpit class' functions and variables from within the LM simply use the vc interface declared above and demonstrated below...

Code:
// --------------------------------------------------------------
// Post-Step processes
// --------------------------------------------------------------
void LM::clbkPostStep (double simt, double simdt, double mjd)
{
[COLOR="red"]	vc->PostStep (simt, simdt, mjd);
[/COLOR]

Compile and make sure you didn't break anything.

In part 14 we'll learn how to make our new cockpit class actually do sh*t.
 
Top