Tutorial Coding a Lunar Lander from the ground up (when you don't know how to code)

Hlynkacg

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

picture.php


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".

picture.php

*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.

picture.php


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)

32585217.jpg


...everything above them Will be going into our header file so get with the Cutting and Pasting.

picture.php


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.

picture.php


: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
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.

Code:
	// associate a mesh for the visual
	mesh_Descent	= AddMesh( mh_descent, &LM_DES_OFFSET);
	mesh_Ascent		= AddMesh( mh_ascent, &LM_ASC_OFFSET);
the "&" indicates a pointer.

Lets recompile and look again...

picture.php


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.
 

wehaveaproblem

One step closer
Addon Developer
Donator
Joined
May 18, 2008
Messages
913
Reaction score
0
Points
16
Location
London
Website
wehaveaproblem.wordpress.com
Cheers for this. I code a bit with lesser languages to some level of competence, but I've always found any language with a C in the name too daunting. But this "dummy's guide" might be just what I need to at least attempt some fundamentals in orbiter. Looking forward to the next instalment.

Cheers
 

DaveS

Addon Developer
Addon Developer
Donator
Beta Tester
Joined
Feb 4, 2008
Messages
9,429
Reaction score
680
Points
203
But this "dummy's guide" might be just what I need to at least attempt some fundamentals in orbiter. Looking forward to the next instalment.

Cheers
Same here! Found it very simple and easy to follow. I can't wait for the next part!
 

Hlynkacg

Aspiring rocket scientist
Addon Developer
Tutorial Publisher
Donator
Joined
Dec 27, 2010
Messages
1,870
Reaction score
3
Points
0
Location
San Diego
So now our ShuttlePB looks like a Lunar Lander...

I know I said that we'd talk about animations next, but as I was writing this I realised that even doing something comparatively simple like making the EVA Hatch open and close requires us to understand a bit about the way that orbiter controls individual vessels.

PART 3: Call Back Functions

Call Back functions are used by the Orbiter core to update a vessel's state within the simulation and notify it of events. These functions will run without any input from you (the addon developer) but by [ame="http://en.wikipedia.org/wiki/Function_overloading"]overloading[/ame] them we can add additional custom behaviors and states to our vessel.

It should be noted that our vessel already overload ones of Orbiter's default Call Back functions, "clbkSetClassCaps". Without any input from us, Orbiter's core would have simply read whatever data it could from our vessel's config file. This is the principal behind "Config-based vessels" like the Carina as well as SC3's *.ini derived vessels.

Anyway a full list of Orbiter's default callback functions and what they do is included in the API documentation but the one that we are interested in at the moment is "clbkPostStep".

clbkPostStep step is called at the end of every simuation frame (time-step) and is ordinarily used to update a vessel's satus/state. Seeing as animation is essentially nothing more than manipulating the state of a mesh over time one should be able to see how this function could be useful to us.

To overload clbkPostStep go to your vessel's class definition/interface in the header file and add the following line.

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

	// Overloaded callback functions
	void	clbkSetClassCaps (FILEHANDLE cfg);
	[COLOR="red"]void	clbkPostStep(double simt, double simdt, double mjd);[/COLOR]

private:
	MESHHANDLE	mh_descent, mh_ascent;	// Mesh handles
	UINT		mesh_Descent;			// Descent stage mesh index
	UINT		mesh_Ascent;			// Ascent stage mesh index
};
NOTE: I'm using "LM" as the name of my vessel, unless you're planning to nest your vessel within a larger project with it's one directories/file-structure I'd recomend choosing a longer and more distinctive name.

then in your source file, below (but not inside) your "clbkSetClassCaps" add the following...

Code:
	// associate a mesh for the visual
	mesh_Descent	= AddMesh( mh_descent, &LM_DES_OFFSET);
	mesh_Ascent	= AddMesh( mh_ascent, &LM_ASC_OFFSET);	

} // End "LM::clbkSetClassCaps"

[COLOR="red"]// --------------------------------------------------------------
// Manage Animations and Post-Step processes
// --------------------------------------------------------------
void LM::clbkPostStep (double simt, double simdt, double mjd)
{

} // End "LM::clbkPostStep"
[/COLOR]

You have now overloaded clbkPostStep, compile your code to make sure you didn't break anything.

Nothing should happen when you open the simulation because you haven't given clbkPostStep anything to do, but we'll take care of that in a moment.

PART 4: Process Values, Enumerators, and Logic Gates

Once again, as per wikipedia, an Enumerator is a data type consisting of a set of named values called elements. A variable that has been declared as having an enumerated type can be assigned any of the enumerators as a value. In other words, an enumerated type has values that are different from each other, and that can be compared and assigned, but which do not have a particular concrete representation in the computer's memory; compilers and interpreters can represent them arbitrarily. (C++ uses integers, 0,1,2,3,etc...)

For example, the four suits in a deck of playing cards may be a set of four elements named CLUB (0), DIAMOND (1), HEART (2), and SPADE (3), belonging to an enumerated type named "suit". If a variable V is declared having suit as its data type, one can assign any of those four values to it.


A "Process Value" in this case is simply a decimal number representing how far-along a certain process is.

So let's go to our back to our class interface and add both an enumerator and a process value.

Code:
private:
[COLOR="red"]	enum		doorstate {CLOSED, OPEN, CLOSING, OPENING} HatchStatus;
	double		Hatch_Proc;[/COLOR]


	MESHHANDLE	mh_descent, mh_ascent;	// Mesh handles
	UINT		mesh_Descent;			// Descent stage mesh index
	UINT		mesh_Ascent;			// Ascent stage mesh index
};

Now because we are animating the EVA Hatch I named my enumerator's data type "doorstate". and the actual variable that we'll be manipulating "HatchStatus" (the names of the individual elements "CLOSED", "OPEN", etc... should be self-explanitory). "Hatch_Proc" will be our process value.

We are declaring them inside our class interface because by declaring it here we ensure that each instance of our vessel in the simulation will have it's own value for "HatchStatus" and "Hatch_Proc" and make sure that these variables will be visible to any function or sub-function associated with that vessel.

Now let's go back to our "clbkPostStep" in the source file.

The following set of logic gates will control our hatch.
Code:
	// EVA hatch control logic
	if (hatch_proc < 0)			// If process value is less than 0...
	{
		HatchStatus = CLOSED;	// ...set status to "CLOSED"
		hatch_proc = 0;		// and process value to 0
	}

	else if (hatch_proc > 1)	// If process value is greater than 1...
	{
		HatchStatus = OPEN;	// ...set status to "OPEN"
		hatch_proc = 1;		// and process value to 1
	}

	if (HatchStatus > CLOSED)	
	{
		double	delta = simdt / 3;	

		if (HatchStatus == OPENING)		// if Status equals "OPENING"...
		{
			hatch_proc += delta;	// ...add delta to process value
		}
		
		if (HatchStatus == CLOSING)		// if Status equals "CLOSING"...
		{
			hatch_proc -= delta;	// ...subtract it.
		}
	}

	// Debuggery
	sprintf(oapiDebugString(), "Hatch Status %0.0f, hatch_proc %0.3f", (float)HatchStatus, hatch_proc);

I've done my best to label everything with comments but let's break it down anyway...

Code:
	// EVA hatch control logic
	if (hatch_proc < 0)			// If process value is less than 0...
	{
		HatchStatus = CLOSED;	// ...set status to "CLOSED"
		hatch_proc = 0;			// and process value to 0
	}

	else if (hatch_proc > 1)	// If process value is greater than 1...
	{
		HatchStatus = OPEN;		// ...set status to "OPEN"
		hatch_proc = 1;			// and process value to 1
	}

This part is simple, it restricts "hatch_proc" to values between 0 and 1 and sets "HatchStatus" accordingly. In essance what we are doing is setting up hatch_proc as a decimal percentage of "how open" the hatch is. If the hatch is 0% (or less) open "HatchStatus" equals "CLOSED". If the hatch is 100% open then "HatchStatus" equals "OPEN"

Moving on...

Code:
	if (HatchStatus > CLOSED)	
	{
		double	delta = simdt / 3;	

		if (HatchStatus == OPENING)		// if Status equals "OPENING"...
		{
			hatch_proc += delta;			// ...add delta to process value
		}
		
		if (HatchStatus == CLOSING)		// if Status equals "CLOSING"...
		{
			hatch_proc -= delta;	// ...subtract it.
		}
	}

sprintf(oapiDebugString(), "Hatch Status %0.0f, hatch_proc %0.3f", (float)HatchStatus, hatch_proc);
NOTE: "hatch_proc -= delta" is equivelent to writing "hatch_proc = hatch_proc - delta"

Now things get a bit more complex. If the value of "HatchStatus" is greater than "CLOSED" it must be either "OPENING" or "CLOSING". As such we need to modify the process value.

"delta" is how quickly we want the value of hatch_proc to change per simulation frame/time-step. As a general rule this value will be equal to the length of the time-step "simdt" divided by the duration of the animation. (3 seconds, in this example)

From here what we are doing should be apparent. If the hatch is opening, we add "delta" to the process value, if not, we subtract it.

"oapiDebugString" is an orbiter function that prints a string of characters, or set of inputed variables in a white box along the bottom right corner of the simualtion window. It is based on C++'s standard "sprintf" function and follows the same rules for formating.

In this case we will be using it to monitor the values of "HatchStatus" and "hatch_proc" to make sure that our code is performing as predicticted.

lets add our hatch logic to "clbkPostStep" and make sure it compiles.

picture.php

NOTE: My lander is sinking into the Earth because it's still using the ShuttlePB's touchdown points. I should probably do something about that. :rolleyes:

So by looking at the debug string we can see that our hatch logic is in there and that as far as we can tell it is working. However, we have no way to manipulate it.

PART 5: Custom User Inputs

In order to make the hatch open or clsoe we need to be able to TELL it to open or close, this requires overloading yet another call back function. "clbkConsumeBufferedKey" is the call back function that orbiter uses to track key board inputs. By overloading it we can change orbiter's default keyboard comands as well as add new ones.

In this specific case we are going to make so that pressing "K" opens and closes the hatch.

Follow the same steps we did to overload clbkPostStep. declare it in your class interface...

Code:
	// Overloaded callback functions
	void	clbkSetClassCaps (FILEHANDLE cfg);								// Set the capabilities of the vessel class
	void	clbkPostStep(double simt, double simdt, double mjd);			// Manage Animations and Post-Step processes
	[COLOR="Red"]int		clbkConsumeBufferedKey (DWORD key, bool down, char *kstate);	// Process keyboard inputs[/COLOR]
private:

...and then add it to your source file.

Code:
	// Debuggery
	sprintf(oapiDebugString(), "Hatch Status %0.0f, hatch_proc %0.3f", (float)HatchStatus, hatch_proc);

} // End "LM::clbkPostStep"

[COLOR="Red"]// --------------------------------------------------------------
// Process keyboard inputs
// --------------------------------------------------------------
int  LM::clbkConsumeBufferedKey (DWORD key, bool down, char *kstate)
{

	return 0; // if no keys are pressed the function returns '0' and nothing happens
}[/COLOR]

clbkConsumeBufferedKey is an int function and works a bit differently from the voids we've been using thus far. This function must return a value, if it doesn't you will get all sorts of errors and possibly a CTD when you try to compile or run it.

Orbiter recognizes two valid values for clbkConsumeBufferedKey, 0 and 1. As you can see the function is currently returning '0' which Orbiter's core interprets as "Carry on, nothing to see here".

NOTE: it is important that "return 0;" remain the last line in the function. If we tell orbiter "nothing to see here" at the beginning of the function orbiter will assume that there is nothing to see, and as such ignore everything that comes after it.

So let's give it something to see, add the following to clbkConsumeBufferedKey (above the "return 0" line)

Code:
	// Open hatch when [K] is pressed.
	if (key == OAPI_KEY_K  && down && !KEYMOD_SHIFT(kstate) && !KEYMOD_CONTROL (kstate) && !KEYMOD_ALT(kstate)) // [K] is down, no [shift], no [ctrl], no [alt]
	{
		if (HatchStatus == CLOSED) HatchStatus = OPENING;	// If the hatch is closed, open it
		else HatchStatus = CLOSING;							// If not, close it 
		return 1;
	}

Once again, I've done my best to label everything but I'll break it down anyway...

The first (very long) if statement is reading the sate of the keyboard. the K key is being pressed down, the Shift key is not, etc...

Then, if all the above conditions are met we activate the control logic. If the hatch is closed, open it. Otherwise, close it.

Finally, returning a value of "1" tells orbiter "Hey! there's :censored: going on here"

Compile and test...

picture.php


If everything has been assembled correctly the value of HatchStatus should change and hatch_proc start start counting up (or down) when you press the K key. Likewise the amount of time it takes to complete a cycle should match the duration assigned to it back in clbkPostStep.

Mess around with it a bit.

The basic framework we've built here can be used for pretty much anything time-based. From animations like opening a hatch/deploying landing gear, to depressurization of an airlock, to something even more complex like a countdown or ignition sequence.

Any way it's getting late and this post has gone on a lot longer than I expected so I'm calling it a night.

Part 6 will be animations, I promise.

---------- Post added at 11:42 PM ---------- Previous post was at 07:13 AM ----------

Now that we've laid the ground-work let's start putting things in motion.

PART 6: Animations and Mesh Groups

As you should already be aware. Orbiter breaks its meshes into "groups". Orbiter performs Animations by transforming these groups using the "MGROUP_TRANSFORM" class. As such, all parts of the mesh participating in an animation must be defined as separate groups from the vessel proper. Multiple groups can participate in a single animation.

Our hatch animation is going to be comprised of two components. The first is the turning of the hatch handle, the second is the actual opening of the hatch. As such I made sure to define the hatch and it's handle as two groups from the seperate rest of the ascent stage.

Orbiter's "MGROUP_TRANSFORM" class is further divided into three subclasses/functions, "TRANSLATE", "ROTATE", and "SCALE". We will be focusing on ROTATE but further explanation of these functions and how they work can be found in "API_Guide.pdf" located in the OrbiterSDK/doc folder.

Let's get to it...

due to the way Orbiter's Core handles animations a vessel's need to be declared immediatly on creation. Trying to declare them on the fly, after the simulation is already in progess will only lead to instability and sorrow, and bugs. As such we will be declaring our animations from within our vessel's constructor function. (You remember where to find it, yes?)

MGROUP_ROTATE's documentation in the API_Guide gives us the basic format to use but let's review it.

We declare it in much the same way we would declare a variable or call back function only we use "MGROUP_ROTATE" as the keyword, we asign a name to it, and then (in perenthesis) give it the set of values to work with.

These values are...
the mesh we are manipulating.
the specific group/s within that mesh to be manipulated
the number of groups being manipulated
the origin (pivot) point of the rotation relative to the mesh
the axis of rotation
and finally the amount of rotation (in radians)

It is very important that ALL of these values be declared, and in the proper order. Failure to do so (best-case) will cause the animation to not work or (worst-case) cause a CTD.

So lets write a "MGROUP_ROTATE" that will make our hatch open...

Code:
	// EVA Hatch animation
	static UINT HatchGrp	= 8;		// participating groups
	
	static MGROUP_ROTATE	mgt_Hatch ( mesh_Ascent, &HatchGrp, 1, _V( 0.394,-0.578, 1.661), _V( 0.0, 1.0, 0.0),	(float)-90*RAD);
NOTE: Ordinarily Orbiter (and C++ programs in general) will delete/forget anything declared within a function once that function is complete. Under normal circumstances this conserves memory and prevents the generation of multiple conflicting instructions. However, we do not want our animation to be forgotten so we've add a "static" keyword to the front of it so that Orbiter understands "hey this is important, so save it". You should be very careful about where you use "static" because it can cause all sort of messy bugs/crashes if they start to pile up on eachother.

Anyway let's look at our function...

"HatchGrp" is a label that we will be using for our hatch's mesh group (8th group in the ascent stage's mesh).

I've named the rotation function itself "mgt_Hatch" and assigned the following values...

  1. Mesh to be manipulated = mesh_Ascent

    NOTE: this is why I assigned indices to my meshes back in PART 2, I can simply reference the mesh by name rather than having to assign a number value to it or generate a handle on the fly.
  2. Group/s to be manipulated = HatchGrp
  3. Number of groups being manipulated = 1
  4. The origin (pivot) point (x,y,z coordinates) = 0.394,-0.578, 1.661
  5. The axis of rotation = Y (vertical)
  6. Amount of rotation =-90 degrees (convert to radians)

Ok we now have a rotation function but as with our hatch logic back in Part 4 we have no way to control it. To fix this we need to declare an animation in our vessel class and add mgt_Hatch to it as a component.

So lets pop back up to our header file and do so...

Code:
private:
	// Variables
	enum		doorstate {CLOSED, OPEN, CLOSING, OPENING} HatchStatus, GearStatus;
	double		hatch_proc, gear_proc;

[COLOR="Red"]	// Animations
	UINT		anim_Hatch, anim_Gear;[/COLOR]

	// Meshes
	MESHHANDLE	mh_descent, mh_ascent;	// Mesh handles
	UINT		mesh_Descent;			// Descent stage mesh index
	UINT		mesh_Ascent;			// Ascent stage mesh index

}; // End "class LM:"
NOTE: you may have noticed that I've added references to "Gear" as well, I gaven't actually done anything with them yet but i'm planning ahead ;).

Now that our animation is declared return to the constructor and add mgt_hatch to it.

Code:
	// EVA Hatch animation
	static UINT HatchGrp	= 8;	// participating groups
	static MGROUP_ROTATE	mgt_Hatch ( mesh_Ascent, &HatchGrp, 1, _V( 0.394,-0.578, 1.661), _V( 0.0, 1.0, 0.0),	(float)-90*RAD); 
	
[COLOR="red"]	anim_Hatch = CreateAnimation(0);
	AddAnimationComponent ( anim_Hatch, 0.0f, 1.0f, &mgt_Hatch);[/COLOR]

the first line commits anim_Hatch to the vessel and sets it's default (starting) position. NOTE: This position corrisponds to the group's state in the mesh file itself. Our ascent stage mesh show the hatch as being closed when you initially load it therefore the starting position should be 0. If the hatch were open the starting position would be 1.

The second line adds mgt_Hatch to our animation and declares it's start/end points (0 and 1).

We now have a way to control our animation. all that's left now, is to actually control it.

drop down to our hatch logic in "clbkPostStep" and add this line...

Code:
	if (HatchStatus > CLOSED)	
	{
		double	delta = simdt / 3;	

		if (HatchStatus == OPENING)				// if Status equals "OPENING"...
		{
			hatch_proc += delta;				// ...add delta to process value
		}
		
		if (HatchStatus == CLOSING)				// if Status equals "CLOSING"...
		{
			hatch_proc -= delta;				// ...subtract it.
		}

[COLOR="red"]		SetAnimation( anim_Hatch, hatch_proc);	// Apply process value to animation.[/COLOR]
	}

Like the comment says, this will apply our process value to anim_Hatch.

Compile and test...

picture.php


Success! :thumbup:

Now obviously the fact that the handle is just hanging there not attatched to the hatch is a bit of a problem Our next part will cover how till fix this.

For the moment though, we can see that our basic principals are sound.

and because it's new years I have an apartment to clean and a party to go to I'll be signing off for now.
 
Last edited:

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 had written a full update but then my browser crashed and ate it.

:compbash2:

I don't feel like spending another hour rewriting the whole thing so this is going to be quick.

First off, I made a mistake in part 6 that may cause in part 7. When our animation is defined in the constructor "mesh_Ascent" is not attatched to anything and wont be until we call the "addmesh" function in clbkSetClassCaps. This will cause what we are about to do in part 7 to not work.

To fix this we must do one of three things

  1. Replace "mesh_Ascent" in the animation's definition with the number 1, thus applying all tranformations to the second mesh to be loaded. (C++ starts counting at 0 so that when declaring an order 0 is first, 1 second, 2 third, etc...)
  2. Cut/Paste the animation's definition to clbkSetClassCaps and place it somewhere down stream of (below) our "addmesh" functions
  3. overload yet another call back function called "clbkPostCreation"

Because I already had plans to use "clbkPostCreation" for something else I will be using option 3.

PART 7: Animation Relationships and Sequencing

Making the handle turn when we open the hatch and fixing our flying door handle bug will involve adding an additional component to "anim_Hatch". Because we will be working with multiple components, we should declare handles (the programming type not extra hatch handles) for them.

Let's pop back up to our class interface in the header file and do so...
Code:
	// Animations
	[COLOR="Red"]ANIMATIONCOMPONENT_HANDLE	ach_Hatch, ach_Handle;[/COLOR]
	UINT						anim_Hatch, anim_Gear;

	// Meshes
	MESHHANDLE	mh_descent, mh_ascent;	// Mesh handles
	UINT		mesh_Descent;			// Descent stage mesh index
	UINT		mesh_Ascent;			// Ascent stage mesh index

}; // End "class LM:"

the names should be self-explanatory.

Now lets look at our animation definition.

Code:
// --------------------------------------------------------------
// Finalise vessel creation
// --------------------------------------------------------------
void LM::clbkPostCreation ()
{
	// EVA Hatch animation
	static UINT	HatchGrp = 8;				// participating groups
	static UINT	HatchHandleGrp = 14;

	static MGROUP_ROTATE	mgt_Hatch (mesh_Ascent, &HatchGrp, 1, _V( 0.394,-0.578, 1.661), _V( 0.0, 1.0, 0.0), (float)-90*RAD); 
	static MGROUP_ROTATE	mgt_HatchHandle (mesh_Ascent, &HatchHandleGrp, 1, _V(-0.40279,-0.55598, 1.69760), _V( 0.0, 0.0, 1.0), (float) 90*RAD);

	anim_Hatch	= CreateAnimation (0.0);
	ach_Hatch	= AddAnimationComponent (anim_Hatch, 0.3f, 1.0f, &mgt_Hatch);
	ach_Handle	= AddAnimationComponent (anim_Hatch, 0.0f, 0.2f, &mgt_HatchHandle, ach_Hatch);
}

As you can see, there have been quite a few changes. but i'll do my best to point them all out and explain them.

My browser ate the entry in which I went step-by-step :compbash: but moving on...

The first and most obvious change is that I've added a second MGROUP_ROTATE for rotating the hatch handle from the down (Closed/Latched) position to the up (open) position.

Code:
void LM::clbkPostCreation ()
{
	// EVA Hatch animation
	static UINT	HatchGrp = 8;				// participating groups
	[COLOR="red"]static UINT	HatchHandleGrp = 14;[/COLOR]

	static MGROUP_ROTATE	mgt_Hatch (mesh_Ascent, &HatchGrp, 1, _V( 0.394,-0.578, 1.661), _V( 0.0, 1.0, 0.0), (float)-90*RAD); 
[COLOR="red"]	static MGROUP_ROTATE	mgt_HatchHandle (mesh_Ascent, &HatchHandleGrp, 1, _V(-0.40279,-0.55598, 1.69760), _V( 0.0, 0.0, 1.0), (float) 90*RAD);[/COLOR]
	anim_Hatch	= CreateAnimation (0.0);
	ach_Hatch	= AddAnimationComponent (anim_Hatch, 0.3f, 1.0f, &mgt_Hatch);
	ach_Handle	= AddAnimationComponent (anim_Hatch, 0.0f, 0.2f, &mgt_HatchHandle, ach_Hatch);
}

I then assigned my ach_Hatch component handle to our existing hatch animation and added our handle's animation to "anim_Hatch" as a second component.

Code:
	anim_Hatch	= CreateAnimation (0.0);
	[COLOR="red"]ach_Hatch[/COLOR]	= AddAnimationComponent (anim_Hatch, 0.3f, 1.0f, &mgt_Hatch);
	[COLOR="red"]ach_Handle	= AddAnimationComponent (anim_Hatch, 0.0f, 0.2f, &mgt_HatchHandle, ach_Hatch);[/COLOR]

To make our handle both turn and move with the rest of the hatch (solving our levetating door handle bug) we need to establish a "Parent/Child" relationship between the opening of the hatch and the turning of the handle. To do this we must include a call to our hatch's animation from within the handle's.

which is what this bit does...

Code:
ach_Handle	= AddAnimationComponent (anim_Hatch, 0.0f, 0.2f, &mgt_HatchHandle[COLOR="red"], ach_Hatch[/COLOR]);

...and why assigning (programing)handles to your components is a good idea. :thumbup:

Finally i messed around with the sequencing. Because I want the handle to turn before the hatch opens, not while it opens, or after.

Code:
	anim_Hatch	= CreateAnimation (0.0);
	ach_Hatch	= AddAnimationComponent (anim_Hatch, [COLOR="red"]0.3f, 1.0f[/COLOR], &mgt_Hatch);
	ach_Handle	= AddAnimationComponent (anim_Hatch, [COLOR="red"]0.0f, 0.2f[/COLOR], &mgt_HatchHandle, ach_Hatch);

The turning of the handle occupys the first 20% of the animation (0.0 to 0.2). the actual opening of the hatch now comprises the remander of the animation (0.3 to 1.0) with a short (0.2 to 0.3) pause in between.

Compile and test...
picture.php


Perfect! :woohoo:

By combining these simple relationships and sequencing principals in creative ways you can make pretty much any form of animation you require.

From hatches, to landing gear, to remote manipulators and centrifuge sections (just set up your process value to loop ;) ) you can create them all using nothing more than what we've already discussed.

If you want to get into something a bit more complex check out my thread here.
It is not a tutorial. It is me trying to learn enough that I night theoretically write a tutorial. But the stuff I'm asking about is directly applicable to this lunar lander and hopefully people will find it educational.

Now pardon me, I'm off to make the parameters of my vessel less ShuttlePBish and more Lunar Lander-like.

Next part will be reading from and writing to scenario files but does any one have any requests for what to cover after that?
 

DaveS

Addon Developer
Addon Developer
Donator
Beta Tester
Joined
Feb 4, 2008
Messages
9,429
Reaction score
680
Points
203
Great work once again! I must point out though that you missed a critical step: Adding void clbkPostCreation(); to the header. Failure to do so results in one warning and two error messages:

Code:
warning C4244: 'argument' : conversion from 'const double' to 'float', possible loss of data
error C2509: 'clbkPostCreation' : member function not declared in 'Grumman_LM'
see declaration of 'Grumman_LM'


---------- Post added at 03:15 AM ---------- Previous post was at 03:11 AM ----------

Next part will be reading from and writing to scenario files but does any one have any requests for what to cover after that?
Well, setting up thrusters could be the part after combined with maybe enhancing control with Thrust Vector Control? Or maybe staging? The LM is after all a two-stage "rocket" with the unusual idea of staging while landed!
 

BruceJohnJennerLawso

Dread Lord of the Idiots
Addon Developer
Joined
Apr 14, 2012
Messages
2,585
Reaction score
0
Points
36
Great work once again! I must point out though that you missed a critical step: Adding void clbkPostCreation(); to the header. Failure to do so results in one warning and two error messages:

Code:
warning C4244: 'argument' : conversion from 'const double' to 'float', possible loss of data
error C2509: 'clbkPostCreation' : member function not declared in 'Grumman_LM'
see declaration of 'Grumman_LM'


---------- Post added at 03:15 AM ---------- Previous post was at 03:11 AM ----------


Well, setting up thrusters could be the part after combined with maybe enhancing control with Thrust Vector Control? Or maybe staging? The LM is after all a two-stage "rocket" with the unusual idea of staging while landed!

Thrust vectoring, and maybe some basic VC & Damage model steps would be good. Outstanding work Hlynkacg, this tutorial is going to be a first step for a lot of future addon devs :thumbup:.
 

Hlynkacg

Aspiring rocket scientist
Addon Developer
Tutorial Publisher
Donator
Joined
Dec 27, 2010
Messages
1,870
Reaction score
3
Points
0
Location
San Diego
Great work once again! I must point out though that you missed a critical step: Adding void clbkPostCreation(); to the header. Failure to do so results in one warning and two error messages:

Code:
warning C4244: 'argument' : conversion from 'const double' to 'float', possible loss of data
error C2509: 'clbkPostCreation' : member function not declared in 'Grumman_LM'
see declaration of 'Grumman_LM'


---------- Post added at 03:15 AM ---------- Previous post was at 03:11 AM ----------


Well, setting up thrusters could be the part after combined with maybe enhancing control with Thrust Vector Control? Or maybe staging? The LM is after all a two-stage "rocket" with the unusual idea of staging while landed!

Good catch. I actually did remember to add it to my header file. What I forgot was to tell the audience that they should do the same :facepalm:.

Thrusters are quick I'll probably do those next instead of Scenario files. After that, I think I will follow your suggestion do staging wich coincidentaly segues nicely into writing scenario files.

---------- Post added at 06:02 AM ---------- Previous post was at 04:33 AM ----------

Ok, I confess...

I wasn't going to post anything about the creation and manipulation of thrusters or control groups because frankly this tutorial here covers the subject far more clearly and in greater detail than I ever could. In fact, my very first Orbiter C++ project in was to follow (and butcher) that tutorial.

That said, I went a little parameter happy :hunter: and it's probably best if I post a complete Header and Source file so that we can all be on the same page as we move up and onward.

so...

Code:
// ==============================================================
//                    ORBITER MODULE: LM
//                  Part of the ORBITER SDK
//               Copyright (C) 2012 Greg Hlynka
//                    All rights reserved
//
// LM.h
// Interface for LM vessel class
//
// Notes: This file exists
// ==============================================================

#define STRICT
#define ORBITER_MODULE

// Orbiter SDK files
#include "orbitersdk.h"

// Mesh resource files
#include "_ascentmesh.h"
#include "_cockpitmesh.h"
#include "_descentmesh.h"

// ==============================================================
// Vessel parameters
// ==============================================================

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
const double	LM_SIZE				= 5.5;					// Vessel's mean radius [m]
const double	LM_ASC_SIZE		= 2.5;					// Mean radius [m] after staging
const VECTOR3	LM_CS				= { 19.0, 19.2, 18.8};	// x,y,z cross sections [m^2]
const VECTOR3	LM_ASC_CS			= { 9.62, 12.6, 9.25};	// Cross sections [m^2] after staging
const VECTOR3	LM_PMI				= { 2.68, 2.10, 2.64};	// Principal moments of inertia (mass-normalised) [m^2]
const VECTOR3	LM_ASC_PMI			= {	1.31, 1.68, 1.25};	// Principal moments of inertia (mass-normalised) [m^2] after staging
const VECTOR3	LM_RD				= {	0.04, 0.05, 0.04};	// Rotation drag coefficients
const VECTOR3	LM_ASC_RD			= {	0.03, 0.07, 0.09};	// Rotation drag coefficients after staging
const double	LM_ASC_EMPTYMASS	= 1922.78;				// Ascent stage empty mass [kg] (4239 lbs, page 119)
const double	LM_DES_EMPTYMASS	= 1798.95;				// Descent stage empty mass [kg] (3,966 lbs, page 119)
const double	LM_ASC_FUELMASS		= 2353.0;				// Ascent stage fuel mass [kg] 
const double	LM_DES_FUELMASS		= 8480.81;				// Descent stage fuel mass [kg] (18,697 lbs, LMFM pg 58)
const double	LM_RCS_FUELMASS		= 750.0;				// RCS fuel mass [kg] (633 lbs, LMFM pg 72)
const double	LM_ASC_WATERMASS	= 19.55;				// max fuel mass [kg] (page 177)
const double	LM_DES_WATERMASS	= 135.64;				// Descent stage water storage mass [kg] (299 lbs, page 177)
const double	LM_ASC_O2MASS		= 1.102;				// max fuel mass [kg] (2.43 lbs)
const double	LM_DES_O2MASS		= 20.50;				// Descent stage O2 reserve mass [kg] (45.2 lbs)
const double	LM_ASC_ISP			= 3050.91;				// Ascent stage specific impulse [m/s] (311 sec, Astronautix.com)
const double	LM_DES_ISP			= 3075.44;				// Descent stage specific impulse [m/s] (313.5 sec, Astronautix.com)
const double	LM_RCS_ISP			= 2844.90;				// RCS specific impulse [m/s] (290 sec, Astronautix.com)
const VECTOR3	LM_TDP[3]			= {{ 0.0,-3.1, 4.4}, {-3.8,-3.1,-2.2}, { 3.8,-3.1,-2.2}};	// touchdown points [m]
const VECTOR3	LM_ASC_TDP[3]		= {{ 0.0,-1.4, 1.7}, {-0.9,-1.4,-1.5}, { 0.9,-1.4,-1.5}};	// touchdown points [m] after staging
const double	LM_ASC_MAXTHRUST	= 15568.8;				// Ascent stage max thrust [n] (3,500 lbf, LMFM pg 58)
const double	LM_DES_MAXTHRUST	= 44482.2;				// Descent stage max thrust	[n] (10,000 lbf, LMFM pg 58)
const double	LM_RCS_MAXTHRUST	= 440.0;				// RCS thruster max thrust [n] (100 lbf)
const VECTOR3	LM_ASC_ENGINEPOS	= {	0.00,-0.64, 0.00};	// Ascent stage engine position [m]
const VECTOR3	LM_DES_ENGINEPOS	= { 0.00,-1.18, 0.00};	// Descent stage  engine position [m]
const VECTOR3	LM_RCS_ENGINEPOS[8]	= {{-1.68, 1.44, 1.68}, {-1.562, 1.44, 1.562}, {-1.68, 1.44,-1.68}, {-1.562, 1.44,-1.562}, { 1.68, 1.44,-1.68}, { 1.562, 1.44,-1.562}, { 1.68, 1.44, 1.68}, { 1.562, 1.44, 1.562}}; // RCS engine positions [m]

const VECTOR3	LM_DOCK_POS			= { 0.00, 2.93, 0.00};	// docking port location [m]
const VECTOR3	LM_DOCK_DIR			= { 0, 1, 0};			// docking port approach direction
const VECTOR3	LM_DOCK_ROT			= { 0, 0, 1};			// docking port alignment direction

// ==============================================================
// Lunar Module class interface
// ==============================================================

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

	// Custom vessel functions

	// Overloaded callback functions
	void	clbkSetClassCaps (FILEHANDLE cfg);								// Set the capabilities of the vessel class
	void	clbkPostCreation ();											// Finalise vessel creation
	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_rcsA, ph_rcsB, ph_ascentFuel, ph_descentFuel;			// Functional Propellant tanks
	PROPELLANT_HANDLE	ph_ascentO2, ph_ascentH2O, ph_descentO2, ph_descentH2O;		// Represenative propellant tanks (consumables)
	
	THRUSTER_HANDLE		th_rcsA[8], th_rcsB[8], th_ascent, th_descent;				// Functional thrusters
	THRUSTER_HANDLE		th_dust, th_vent;											// Represenative thrusters (particle streams)

private:
	// Vessel status variables
		enum	doorstate	{CLOSED, OPEN, CLOSING, OPENING}	HatchStatus, GearStatus;
	
	double	thrust_mod, dv_mod, consumables_mod;

	// Animations
	UINT						anim_Hatch, anim_Gear;
	double						hatch_proc, gear_proc;
	ANIMATIONCOMPONENT_HANDLE	ach_GearLeg[4], ach_GearStrut[4], ach_GearLock[4], ach_Hatch, ach_HatchHandle;
	MGROUP_TRANSFORM			*mgt_Leg[4], *mgt_Strut[4], *mgt_Downlock[4];
	
	// Meshes
	MESHHANDLE	mh_descent, mh_ascent;	// Mesh handles
	UINT		mesh_Descent;			// Descent stage mesh index
	UINT		mesh_Ascent;			// Ascent stage mesh index

}; // End "class LM:"


---------- Post added at 06:20 AM ---------- Previous post was at 06:02 AM ----------

And here's the source...

Code:
// ==============================================================
//                    ORBITER MODULE: LM
//                  Part of the ORBITER SDK
//               Copyright (C) 2012 Greg Hlynka
//                    All rights reserved
//
// LM.cpp
// Control module for LM vessel class
//
// Notes: Writing Tutorials is hard work
// ==============================================================

#include "LM.h"

// ==============================================================
// Class Constructor and Destructor
// ==============================================================

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;

	// Load Exterior meshes
	mh_descent		= oapiLoadMeshGlobal("UMMU_Apollo/LEM_DescentStage");
	mh_ascent		= oapiLoadMeshGlobal("UMMU_Apollo/LEM_AscentStage");
}

LM::~LM ()
{
}

// ==============================================================
// Overloaded callback functions
// ==============================================================

// --------------------------------------------------------------
// Set the capabilities of the vessel class
// --------------------------------------------------------------
void LM::clbkSetClassCaps (FILEHANDLE cfg)
{

	// Physical vessel parameters
	SetSize (LM_SIZE);
	SetEmptyMass (LM_ASC_EMPTYMASS + LM_DES_EMPTYMASS);
	SetPMI (LM_PMI);
	SetCrossSections (LM_CS);
	SetRotDrag (LM_RD);
	SetTouchdownPoints (LM_TDP[0], LM_TDP[1], LM_TDP[2]);

	// Docking port definition
	SetDockParams (LM_DOCK_POS, LM_DOCK_DIR, LM_DOCK_ROT);

	// Propellant resources
	ph_rcsA			= CreatePropellantResource (LM_RCS_FUELMASS/2);		// RCS propellant tank 'A'
	ph_rcsB			= CreatePropellantResource (LM_RCS_FUELMASS/2);		// RCS propellant tank 'B'
	ph_ascentFuel	= CreatePropellantResource (LM_ASC_FUELMASS);		// Ascent stage propellant tank
	ph_ascentO2		= CreatePropellantResource (LM_ASC_O2MASS);			// Ascent stage O2 tank
	ph_ascentH2O	= CreatePropellantResource (LM_ASC_WATERMASS);		// Ascent stage water tank

	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

	// RCS engines: Quadrant 1 (Port Fwd)
	th_rcsA[0]	= CreateThruster (LM_RCS_ENGINEPOS[0], _V( 0,-1, 0), LM_RCS_MAXTHRUST, ph_rcsA, LM_RCS_ISP*dv_mod); // Down
	th_rcsA[1]	= CreateThruster (LM_RCS_ENGINEPOS[1], _V( 0, 0,-1), LM_RCS_MAXTHRUST, ph_rcsA, LM_RCS_ISP*dv_mod); // Back
	th_rcsB[0]	= CreateThruster (LM_RCS_ENGINEPOS[0], _V( 0, 1, 0), LM_RCS_MAXTHRUST, ph_rcsB, LM_RCS_ISP*dv_mod); // Up
	th_rcsB[1]	= CreateThruster (LM_RCS_ENGINEPOS[1], _V( 1, 0, 0), LM_RCS_MAXTHRUST, ph_rcsB, LM_RCS_ISP*dv_mod); // Right

	// RCS engines: Quadrant 2 (Port Aft)
	th_rcsA[2]	= CreateThruster (LM_RCS_ENGINEPOS[2], _V( 0, 1, 0), LM_RCS_MAXTHRUST, ph_rcsA, LM_RCS_ISP*dv_mod); // Up
	th_rcsA[3]	= CreateThruster (LM_RCS_ENGINEPOS[3], _V( 0, 0, 1), LM_RCS_MAXTHRUST, ph_rcsA, LM_RCS_ISP*dv_mod); // Fwd
	th_rcsB[2]	= CreateThruster (LM_RCS_ENGINEPOS[2], _V( 0,-1, 0), LM_RCS_MAXTHRUST, ph_rcsB, LM_RCS_ISP*dv_mod); // Down 
	th_rcsB[3]	= CreateThruster (LM_RCS_ENGINEPOS[3], _V( 1, 0, 0), LM_RCS_MAXTHRUST, ph_rcsB, LM_RCS_ISP*dv_mod); // Right

	// RCS engines: Quadrant 3 (Stbd Aft)
	th_rcsA[4]	= CreateThruster (LM_RCS_ENGINEPOS[4], _V( 0,-1, 0), LM_RCS_MAXTHRUST, ph_rcsA, LM_RCS_ISP*dv_mod); // Down
	th_rcsA[5]	= CreateThruster (LM_RCS_ENGINEPOS[5], _V(-1, 0, 0), LM_RCS_MAXTHRUST, ph_rcsA, LM_RCS_ISP*dv_mod); // Left
	th_rcsB[4]	= CreateThruster (LM_RCS_ENGINEPOS[4], _V( 0, 1, 0), LM_RCS_MAXTHRUST, ph_rcsB, LM_RCS_ISP*dv_mod); // Up
	th_rcsB[5]	= CreateThruster (LM_RCS_ENGINEPOS[5], _V( 0, 0, 1), LM_RCS_MAXTHRUST, ph_rcsB, LM_RCS_ISP*dv_mod); // Fwd

	// RCS engines: Quadrant 4 (Stbd Fwd)
	th_rcsA[6]	= CreateThruster (LM_RCS_ENGINEPOS[6], _V( 0, 1, 0), LM_RCS_MAXTHRUST, ph_rcsA, LM_RCS_ISP*dv_mod); // Up
	th_rcsA[7]	= CreateThruster (LM_RCS_ENGINEPOS[7], _V(-1, 0, 0), LM_RCS_MAXTHRUST, ph_rcsA, LM_RCS_ISP*dv_mod); // Left
	th_rcsB[6]	= CreateThruster (LM_RCS_ENGINEPOS[6], _V( 0,-1, 0), LM_RCS_MAXTHRUST, ph_rcsB, LM_RCS_ISP*dv_mod); // Down
	th_rcsB[7]	= CreateThruster (LM_RCS_ENGINEPOS[7], _V( 0, 0,-1), LM_RCS_MAXTHRUST, ph_rcsB, LM_RCS_ISP*dv_mod); // Back

	// Ascent engine
	th_ascent	= CreateThruster (LM_ASC_ENGINEPOS, _V( 0, 1, 0), LM_ASC_MAXTHRUST*thrust_mod, ph_ascentFuel, LM_ASC_ISP*dv_mod);

	// Descent engine
	th_descent	= CreateThruster (LM_DES_ENGINEPOS, _V( 0, 1, 0), LM_DES_MAXTHRUST*thrust_mod, ph_descentFuel, LM_DES_ISP*dv_mod);	
	
	// Camera parameters
	SetCameraOffset (_V( 0.0, 2.1, 0.0));

	// Associate meshes for the visual
	mesh_Descent	= AddMesh (mh_descent, &LM_DES_OFFSET);
	mesh_Ascent		= AddMesh (mh_ascent, &LM_ASC_OFFSET);	

	SetMeshVisibilityMode (mesh_Descent, MESHVIS_ALWAYS);	// I want the the descent stage's legs to be visible from the cockpit

} // End "LM::clbkSetClassCaps"

// --------------------------------------------------------------
// Finalise vessel creation
// --------------------------------------------------------------
void LM::clbkPostCreation ()
{
	// EVA Hatch animation
	static UINT	meshgroup_Hatch		= AS_GRP_EvaHatch;				// participating groups
	static UINT	meshgroup_Handle	= AS_GRP_HatchHandle;

	static MGROUP_ROTATE	mgt_Hatch (mesh_Ascent, &meshgroup_Hatch, 1, _V( 0.394,-0.578, 1.661), _V( 0.0, 1.0, 0.0), (float)-90*RAD); 
	static MGROUP_ROTATE	mgt_HatchHandle (mesh_Ascent, &meshgroup_Handle, 1, _V(-0.40279,-0.55598, 1.69760), _V( 0.0, 0.0, 1.0), (float) 90*RAD);

	anim_Hatch		= CreateAnimation (0.0);
	ach_Hatch		= AddAnimationComponent (anim_Hatch, 0.3f, 1.0f, &mgt_Hatch);
	ach_HatchHandle	= AddAnimationComponent (anim_Hatch, 0.0f, 0.2f, &mgt_HatchHandle, ach_Hatch);

	// Landing Gear Animation
	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;

	anim_Gear = CreateAnimation (1.0);

	for (int i = 0; i < 4; i++)
	{
		// Animation components
		mgt_Leg[i]		= new MGROUP_ROTATE (mesh_Descent, &meshgroup_Legs[i][0], 3, LM_LegPivot[i], LM_LG_Axis[i], (float) 45*RAD);		// Animate landing legs
		mgt_Strut[i]	= new MGROUP_ROTATE (mesh_Descent, &meshgroup_Struts[i], 1, LM_StrutPivot[i], LM_LG_Axis[i], (float)-63*RAD);		// Animate Support Struts attatched to legs
		mgt_Downlock[i]	= new MGROUP_ROTATE (mesh_Descent, &meshgroup_Locks[i], 1, LM_DownlockPivot[i], LM_LG_Axis[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.0, 1.0, mgt_Leg[i]);
		ach_GearStrut[i]	= AddAnimationComponent (anim_Gear, 0.0, 1.0, mgt_Strut[i], ach_GearLeg[i]);
		ach_GearLock[i]		= AddAnimationComponent (anim_Gear, 0.0, 1.0, mgt_Downlock[i], ach_GearStrut[i]);
	}

	static MGROUP_ROTATE mgt_Ladder (mesh_Descent, &meshgroup_Ladder, 1, LM_LegPivot[0], LM_LG_Axis[0], (float) 45*RAD); // Apply front leg's animation state to ladder.
	AddAnimationComponent (anim_Gear, 0.0, 1, &mgt_Ladder);
} // End "LM::clbkPostCreation"

// --------------------------------------------------------------
// Manage Animations and Post-Step processes
// --------------------------------------------------------------
void LM::clbkPostStep (double simt, double simdt, double mjd)
{

	// EVA hatch control logic
	if (hatch_proc < 0)			// If process value is less than 0...
	{
		HatchStatus = CLOSED;	// ...set status to "CLOSED"
		hatch_proc = 0;			// and process value to 0
	}

	else if (hatch_proc > 1)	// If process value is greater than 1...
	{
		HatchStatus = OPEN;		// ...set status to "OPEN"
		hatch_proc = 1;			// and process value to 1
	}

	if (HatchStatus > CLOSED)	
	{
		double	delta = simdt / 3;	

		if (HatchStatus == OPENING)				// if Status equals "OPENING"...
		{
			hatch_proc += delta;				// ...add delta to process value
		}
		
		if (HatchStatus == CLOSING)				// if Status equals "CLOSING"...
		{
			hatch_proc -= delta;				// ...subtract it.
		}

		SetAnimation (anim_Hatch, hatch_proc);	// Apply process value to animation.
	}

	// Debuggery
	//sprintf(oapiDebugString(), "Hatch Status %0.0f, hatch_proc %0.3f", (float)HatchStatus, hatch_proc);

	// Landing Gear 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;	

		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(), "Gear Status %0.0f, gear_proc %0.3f", (float)GearStatus, gear_proc);

} // End "LM::clbkPostStep"

// --------------------------------------------------------------
// Process keyboard inputs
// --------------------------------------------------------------
int  LM::clbkConsumeBufferedKey (DWORD key, bool down, char *kstate)
{
	// Open hatch when [K] is pressed.
	if (key == OAPI_KEY_K  && down && !KEYMOD_SHIFT(kstate) && !KEYMOD_CONTROL (kstate) && !KEYMOD_ALT(kstate)) // [K] is down, no [shift], no [ctrl], no [alt]
	{
		if (HatchStatus == CLOSED) HatchStatus = OPENING;	// If the hatch is closed, open it
		else HatchStatus = CLOSING;							// If not, close it 
		return 1;											// return '1' to indicate that the input has been processed
	}

	// Deploy Ganding Gear when [G] is pressed.
	if (key == OAPI_KEY_G  && down && !KEYMOD_SHIFT(kstate) && !KEYMOD_CONTROL (kstate) && !KEYMOD_ALT(kstate)) // [K] is down, no [shift], no [ctrl], no [alt]
	{
		if (GearStatus != OPEN) GearStatus = OPENING;		// If landing gear have not been deployed, deploy them
		return 1;
	}

	return 0;	// if no keys are pressed the function returns '0' and nothing happens

} // End "LM::clbkConsumeBufferedKey"

// ==============================================================
// API callback interface
// ==============================================================

// --------------------------------------------------------------
// Vessel initialisation
// --------------------------------------------------------------
DLLCLBK VESSEL *ovcInit (OBJHANDLE hvessel, int flightmodel)
{
	return new LM (hvessel, flightmodel);
}

// --------------------------------------------------------------
// Vessel cleanup
// --------------------------------------------------------------
DLLCLBK void ovcExit (VESSEL *vessel)
{
	if (vessel) delete (LM*)vessel;
}

Note that I've added the landing gear animations. The principals are the same as those in part 7 just with many more moving parts. For an explanation of it's finer points, check out the thread I linked to earlier.

The control logic for the gear is simply a copy-pasta of the hatch's

You'll also note that many of the parameters I've added mention staging. That's what I intend to work on next
 
Last edited:

Hlynkacg

Aspiring rocket scientist
Addon Developer
Tutorial Publisher
Donator
Joined
Dec 27, 2010
Messages
1,870
Reaction score
3
Points
0
Location
San Diego
That's why I >always< write my longer posts with Notepad++ (or anything that can "save" the text).

I normally do this as well but for some reason I just wasn't thinking last night and... :facepalm:

For those of you who have been following along...

Please take a look at the complete Header and Source files I posted above and note the changes.

The first and most obvious change is that I've replaced the SuttlePB's physical parameters with a whole host of new ones that describe the Apollo Lunar Lander.

I then went into clbkSetClassCaps and applied those parameters to the vessel.

I'm not going to do a step-by-step for this because 1) it is really quite simple. 2) it is tedious. and 3) this tutorial here covers the subject far more clearly and in greater detail than I ever could.

That said, I will field questions on the subject so if you have 'em, ask 'em.

NOTE: I have not assigned control groups at this point, I'm actually planning something special in this regard.

Another thing I've added is "dificulty modifiers" at the moment their values are fixed at 1 but the intent is that down the road their values will be read from a config file allowing the end user customise things like exhaust velocity, fuel capacity etc... (stealing a page from the XR fleet :thumbup:) we will go over the actual implimentation of this process in detail later but for the moment we need is a couple of place-holders.

Another subtle (but important) change is the inclusion of three additional header files.

Code:
// Mesh resource files
#include "_ascentmesh.h"
#include "_descentmesh.h"
#include "_cockpitmesh.h"

These files contain a set of definitions that allow us to call individual groups, materials, and textures within a mesh file by name rather than by index number.

Note how the "participating groups" line of our hatch animation has changed from...

Code:
	static UINT	HatchGrp = 8;				// participating groups
	static UINT	HatchHandleGrp = 14;

to...

Code:
	static UINT	meshgroup_Hatch		= AS_GRP_EvaHatch;				// participating groups
	static UINT	meshgroup_Handle	= AS_GRP_HatchHandle;

Easier to read yes?

Several mesh export/converter scripts will automatically generate a header file for this purpose. If yours does not you can also use MeshC in the OrbiterSDK\Utils folder to create a header file for any existing *.msh file.

These are my headers...
Code:
// ========================================================
// Mesh resource file for LM Ascent Stage
// Generated with meshc on Tue Sep 11 13:59:02 2012
// ========================================================

// Number of mesh groups:
#define AS_NGRP 42

// Number of materials:
#define AS_NMAT 14

// Number of textures:
#define AS_NTEX 5

// Named mesh groups:
#define AS_GRP_AftAvionicsCover		0
#define AS_GRP_Antenna				1
#define AS_GRP_AntennaSpars			2
#define AS_GRP_Antennawires			3
#define AS_GRP_CabinRCSMounts		4
#define AS_GRP_CabinStrut			5
#define AS_GRP_DockingPort			6
#define AS_GRP_EngineBell			7
#define AS_GRP_EvaHatch				8
#define AS_GRP_FoilGreebles			9
#define AS_GRP_FwdAvionicsCover		10
#define AS_GRP_Greebles				11
#define AS_GRP_HandRail				12
#define AS_GRP_HatchFoil			13
#define AS_GRP_HatchHandle			14
#define AS_GRP_HullBack				15
#define AS_GRP_HullBack1			16
#define AS_GRP_HullBottom			17
#define AS_GRP_HullCabin			18
#define AS_GRP_HullFront			19
#define AS_GRP_HullMidSection		20
#define AS_GRP_InteriorCabin		21
#define AS_GRP_InteriorDetails		22
#define AS_GRP_InteriorFloor		23
#define AS_GRP_InteriorMidSection	24
#define AS_GRP_Lights				25
#define AS_GRP_Nameplate			26
#define AS_GRP_RCSNozzles			27
#define AS_GRP_RCSQuads				28
#define AS_GRP_RadarAntenna			29
#define AS_GRP_RadarDish			30
#define AS_GRP_RadarMount			31
#define AS_GRP_RadarPivot			32
#define AS_GRP_Radiator				33
#define AS_GRP_SBandAntenna			34
#define AS_GRP_SBandDish			35
#define AS_GRP_SBandPivot			36
#define AS_GRP_TargetBase			37
#define AS_GRP_TargetShaft			38
#define AS_GRP_ThrustChambers		39
#define AS_GRP_WindowFrames			40
#define AS_GRP_WindowGlass			41

Code:
// ========================================================
// Mesh resource file for LM Descent Stage
// Generated with meshc on Wed Sep 12 10:28:11 2012
// ========================================================

// Number of mesh groups:
#define NGRP 36

// Number of materials:
#define NMAT 8

// Number of textures:
#define NTEX 4

// Named mesh groups:
#define DS_GRP_AscentHardpoints 0
#define DS_GRP_AvionicsCover 1
#define DS_GRP_DeflectorStruts 2
#define DS_GRP_DescentBody 3
#define DS_GRP_DescentBottom 4
#define DS_GRP_Downlock_AFT 5
#define DS_GRP_Downlock_FWD 6
#define DS_GRP_Downlock_PORT 7
#define DS_GRP_Downlock_STBD 8
#define DS_GRP_EngineBell 9
#define DS_GRP_EngineGimbal 10
#define DS_GRP_EngineShroud 11
#define DS_GRP_GearHardpoints 12
#define DS_GRP_GearSupports 13
#define DS_GRP_Handrails 14
#define DS_GRP_Ladder 15
#define DS_GRP_LandingFoot_AFT 16
#define DS_GRP_LandingFoot_FWD 17
#define DS_GRP_LandingFoot_PORT 18
#define DS_GRP_LandingFoot_STBD 19
#define DS_GRP_Outriggers 20
#define DS_GRP_Porch 21
#define DS_GRP_PrimaryStrut_AFT 22
#define DS_GRP_PrimaryStrut_FWD 23
#define DS_GRP_PrimaryStrut_PORT 24
#define DS_GRP_PrimaryStrut_STBD 25
#define DS_GRP_SecondaryStruts_AFT 26
#define DS_GRP_SecondaryStruts_FWD 27
#define DS_GRP_SecondaryStruts_PORT 28
#define DS_GRP_SecondaryStruts_STBD 29
#define DS_GRP_ShockStrut_AFT 30
#define DS_GRP_ShockStrut_FWD 31
#define DS_GRP_ShockStrut_PORT 32
#define DS_GRP_ShockStrut_STBD 33
#define DS_GRP_ThrustChamber 34
#define DS_GRP_ThrustDefelectors 35

// Descent stage animation mesh coordinates
const VECTOR3	LM_LegAxis[4]			= { {-1, 0, 0}, { 1, 0, 0}, { 0, 0,-1}, { 0, 0, 1}};
const VECTOR3	LM_LegPivot[4]			= { { 0.00, 0.42465, 2.88195}, { 0.00, 0.42465,-2.88195}, {-2.88195, 0.42465, 0.00}, { 2.88195, 0.42465, 0.00}};
const VECTOR3	LM_StrutPivot[4]		= { { 0.00,-1.19950, 3.62660}, { 0.00,-1.19950,-3.62660}, {-3.62660,-1.19950, 0.00}, { 3.62660,-1.19950, 0.00}};
const VECTOR3	LM_DownlockPivot[4]		= { { 0.00,-1.05769, 2.83017}, { 0.00,-1.05769,-2.83017}, {-2.83017,-1.05769, 0.00}, { 2.83017,-1.05769, 0.00}};

NOTE: I added the animation coordinates to the end by hand.

Code:
// ========================================================
// Mesh resource file for LM Virtual Cockpit
// Generated with meshc on Sun Nov 11 14:20:19 2012
// ========================================================

// Number of mesh groups:
#define VC_NGRP 238

// Number of materials:
#define VC_NMAT 14

// Number of textures:
#define VC_NTEX 10

// Named mesh groups:
#define VC_GRP_ADI_Interior 0
#define VC_GRP_Alt_Rate 1
#define VC_GRP_Altitude 2
#define VC_GRP_Button_P5_01 3
#define VC_GRP_CabinRCSMounts 4
#define VC_GRP_Cylinder 5
#define VC_GRP_Cylinder002 6
#define VC_GRP_Cylinder003 7
#define VC_GRP_EvaHatch 8
#define VC_GRP_FwdAvionicsCover 9
#define VC_GRP_HandRail 10
#define VC_GRP_HatchFoil 11
#define VC_GRP_HatchHandle 12
#define VC_GRP_Hull 13
#define VC_GRP_Inst_COAS_Plane 14
#define VC_GRP_Inst_CWA 15
#define VC_GRP_Inst_LCDs 16
#define VC_GRP_Inst_Panel1_Gauges 17
#define VC_GRP_Inst_Panel1_Lights 18
#define VC_GRP_Inst_Xpointer 19
#define VC_GRP_InteriorCabin 20
#define VC_GRP_InteriorFloor 21
#define VC_GRP_InteriorMidSection 22
#define VC_GRP_MFD_Cmd 23
#define VC_GRP_MFD_Frame 24
#define VC_GRP_MFD_buttons 25
#define VC_GRP_OvhdWindow 26
#define VC_GRP_Panel_01 27
#define VC_GRP_Panel_03 28
#define VC_GRP_Panel_04 29
#define VC_GRP_Panel_05 30
#define VC_GRP_Panel_06 31
#define VC_GRP_Panel_08 32
#define VC_GRP_Panel_12 33
#define VC_GRP_Panel_14 34
#define VC_GRP_Panel_ADI_Housing 35
#define VC_GRP_Panel_Needle 36
#define VC_GRP_Panel_Needle001 37
#define VC_GRP_Panel_Needle002 38
#define VC_GRP_Panel_Needle003 39
#define VC_GRP_Panel_Needle004 40
#define VC_GRP_Panel_Needle005 41
#define VC_GRP_Panel_Needle006 42
#define VC_GRP_Panel_Needle007 43
#define VC_GRP_Panel_Needle008 44
#define VC_GRP_Panel_Needle009 45
#define VC_GRP_Panel_Needle010 46
#define VC_GRP_Panel_Needle011 47
#define VC_GRP_Panel_Needle012 48
#define VC_GRP_Panel_Needle013 49
#define VC_GRP_Panel_Needle014 50
#define VC_GRP_Panel_Needle015 51
#define VC_GRP_Panel_Needle016 52
#define VC_GRP_Panel_Needle017 53
#define VC_GRP_Panel_Needle018 54
#define VC_GRP_Panel_NeedlePitch 55
#define VC_GRP_Panel_NeedleRoll 56
#define VC_GRP_Panel_NeedleYaw 57
#define VC_GRP_Panel_Switches 58
#define VC_GRP_RCSNozzles 59
#define VC_GRP_RCSQuads 60
#define VC_GRP_Dial_P12_01 61
#define VC_GRP_Dial_P12_02 62
#define VC_GRP_Dial_P12_03 63
#define VC_GRP_Dial_P12_04 64
#define VC_GRP_Dial_P14_01 65
#define VC_GRP_Dial_P1_01 66
#define VC_GRP_Dial_P2_01 67
#define VC_GRP_Dial_P2_02 68
#define VC_GRP_Dial_P2_03 69
#define VC_GRP_Dial_P2_04 70
#define VC_GRP_Dial_P3_01 71
#define VC_GRP_Dial_P3_02 72
#define VC_GRP_Dial_P3_03 73
#define VC_GRP_Dial_P3_04 74
#define VC_GRP_Dial_P3_05 75
#define VC_GRP_Dial_P3_06 76
#define VC_GRP_Dial_P5_01 77
#define VC_GRP_Dial_P5_02 78
#define VC_GRP_Dial_P5_03 79
#define VC_GRP_Dial_P6_00 80
#define VC_GRP_Dial_P6_01 81
#define VC_GRP_Dial_P6_02 82
#define VC_GRP_Dial_P6_03 83
#define VC_GRP_Switch_P12_01 84
#define VC_GRP_Switch_P12_02 85
#define VC_GRP_Switch_P12_03 86
#define VC_GRP_Switch_P12_04 87
#define VC_GRP_Switch_P12_05 88
#define VC_GRP_Switch_P12_06 89
#define VC_GRP_Switch_P12_07 90
#define VC_GRP_Switch_P12_08 91
#define VC_GRP_Switch_P12_09 92
#define VC_GRP_Switch_P12_10 93
#define VC_GRP_Switch_P12_11 94
#define VC_GRP_Switch_P12_12 95
#define VC_GRP_Switch_P12_13 96
#define VC_GRP_Switch_P12_14 97
#define VC_GRP_Switch_P12_15 98
#define VC_GRP_Switch_P12_16 99
#define VC_GRP_Switch_P12_17 100
#define VC_GRP_Switch_P12_18 101
#define VC_GRP_Switch_P12_19 102
#define VC_GRP_Switch_P12_20 103
#define VC_GRP_Switch_P12_21 104
#define VC_GRP_Switch_P12_22 105
#define VC_GRP_Switch_P14_01 106
#define VC_GRP_Switch_P14_02 107
#define VC_GRP_Switch_P14_03 108
#define VC_GRP_Switch_P14_04 109
#define VC_GRP_Switch_P14_05 110
#define VC_GRP_Switch_P14_06 111
#define VC_GRP_Switch_P14_07 112
#define VC_GRP_Switch_P14_08 113
#define VC_GRP_Switch_P14_09 114
#define VC_GRP_Switch_P14_10 115
#define VC_GRP_Switch_P14_11 116
#define VC_GRP_Switch_P14_12 117
#define VC_GRP_Switch_P14_13 118
#define VC_GRP_Switch_P14_14 119
#define VC_GRP_Switch_P14_15 120
#define VC_GRP_Switch_P14_16 121
#define VC_GRP_Switch_P1_00 122
#define VC_GRP_Switch_P1_01 123
#define VC_GRP_Switch_P1_02 124
#define VC_GRP_Switch_P1_03 125
#define VC_GRP_Switch_P1_04 126
#define VC_GRP_Switch_P1_05 127
#define VC_GRP_Switch_P1_06 128
#define VC_GRP_Switch_P1_07 129
#define VC_GRP_Switch_P1_08 130
#define VC_GRP_Switch_P1_09 131
#define VC_GRP_Switch_P1_10 132
#define VC_GRP_Switch_P1_11 133
#define VC_GRP_Switch_P1_12 134
#define VC_GRP_Switch_P1_13 135
#define VC_GRP_Switch_P1_14 136
#define VC_GRP_Switch_P1_15 137
#define VC_GRP_Switch_P1_16 138
#define VC_GRP_Switch_P1_17 139
#define VC_GRP_Switch_P1_18 140
#define VC_GRP_Switch_P1_19 141
#define VC_GRP_Switch_P2_01 142
#define VC_GRP_Switch_P2_02 143
#define VC_GRP_Switch_P2_03 144
#define VC_GRP_Switch_P2_04 145
#define VC_GRP_Switch_P2_05 146
#define VC_GRP_Switch_P2_06 147
#define VC_GRP_Switch_P2_07 148
#define VC_GRP_Switch_P2_08 149
#define VC_GRP_Switch_P2_09 150
#define VC_GRP_Switch_P2_10 151
#define VC_GRP_Switch_P2_11 152
#define VC_GRP_Switch_P2_12 153
#define VC_GRP_Switch_P2_13 154
#define VC_GRP_Switch_P2_14 155
#define VC_GRP_Switch_P2_15 156
#define VC_GRP_Switch_P2_16 157
#define VC_GRP_Switch_P2_17 158
#define VC_GRP_Switch_P2_18 159
#define VC_GRP_Switch_P3_00 160
#define VC_GRP_Switch_P3_01 161
#define VC_GRP_Switch_P3_02 162
#define VC_GRP_Switch_P3_03 163
#define VC_GRP_Switch_P3_04 164
#define VC_GRP_Switch_P3_05 165
#define VC_GRP_Switch_P3_06 166
#define VC_GRP_Switch_P3_07 167
#define VC_GRP_Switch_P3_09 168
#define VC_GRP_Switch_P3_10 169
#define VC_GRP_Switch_P3_12 170
#define VC_GRP_Switch_P3_13 171
#define VC_GRP_Switch_P3_14 172
#define VC_GRP_Switch_P3_15 173
#define VC_GRP_Switch_P3_16 174
#define VC_GRP_Switch_P3_17 175
#define VC_GRP_Switch_P3_18 176
#define VC_GRP_Switch_P3_19 177
#define VC_GRP_Switch_P3_20 178
#define VC_GRP_Switch_P3_21 179
#define VC_GRP_Switch_P3_22 180
#define VC_GRP_Switch_P3_23 181
#define VC_GRP_Switch_P3_24 182
#define VC_GRP_Switch_P3_25 183
#define VC_GRP_Switch_P4_01 184
#define VC_GRP_Switch_P4_02 185
#define VC_GRP_Switch_P4_03 186
#define VC_GRP_Switch_P4_04 187
#define VC_GRP_Switch_P5_01 188
#define VC_GRP_Switch_P5_02 189
#define VC_GRP_Switch_P5_03 190
#define VC_GRP_Switch_P5_04 191
#define VC_GRP_Switch_P5_05 192
#define VC_GRP_Switch_P5_06 193
#define VC_GRP_Switch_P5_07 194
#define VC_GRP_Switch_P5_08 195
#define VC_GRP_Switch_P6_00 196
#define VC_GRP_Switch_P6_01 197
#define VC_GRP_Switch_P6_02 198
#define VC_GRP_Switch_P6_03 199
#define VC_GRP_Switch_P6_04 200
#define VC_GRP_Switch_P6_05 201
#define VC_GRP_Switch_P6_06 202
#define VC_GRP_Switch_P6_07 203
#define VC_GRP_Switch_P6_08 204
#define VC_GRP_Switch_P6_09 205
#define VC_GRP_Switch_P6_10 206
#define VC_GRP_Switch_P6_11 207
#define VC_GRP_Switch_P8_01 208
#define VC_GRP_Switch_P8_02 209
#define VC_GRP_Switch_P8_03 210
#define VC_GRP_Switch_P8_04 211
#define VC_GRP_Switch_P8_05 212
#define VC_GRP_Switch_P8_06 213
#define VC_GRP_Switch_P8_07 214
#define VC_GRP_Switch_P8_08 215
#define VC_GRP_Switch_P8_09 216
#define VC_GRP_Switch_P8_10 217
#define VC_GRP_Switch_P8_11 218
#define VC_GRP_Switch_P8_12 219
#define VC_GRP_Switch_P8_13 220
#define VC_GRP_Switch_P8_14 221
#define VC_GRP_Switch_P8_15 222
#define VC_GRP_Switch_P8_16 223
#define VC_GRP_Switch_P8_17 224
#define VC_GRP_Switch_P8_18 225
#define VC_GRP_Switch_P8_19 226
#define VC_GRP_Switch_P8_20 227
#define VC_GRP_Switch_P8_21 228
#define VC_GRP_Switch_P8_22 229
#define VC_GRP_TelescopeEyepiece 230
#define VC_GRP_TelescopeGuard 231
#define VC_GRP_TelescopeHousing 232
#define VC_GRP_ThrustChambers 233
#define VC_GRP_WindowFrames 234
#define VC_GRP_WindowGlass 235
#define VC_GRP_Window_ADI 236
#define VC_GRP_Window_COAS 237

Mesh resource files are not really important when you're only dealing with 2 or 3 moving parts at a time but as you can see, our VC will have HUNDREDS of them and at that point using index numbers becomes problematic.

Adding and coding the VC is still a long way away but I want to start planning for it now rather than have to go back and re-code a bunch of stuff later. :thumbup:

Anyway...

The next feature I want to impliment is "Staging". The LM is after all a two-stage "rocket" with the rather odd idea of staging while landed! :lol:

However in order to impliment staging we need to be able to create and apply custom vessel states. An important part of this is making sure that these states are persistant. Afterall you don't want the first stage of your launch vehicle "Respawing" every time you load (Current State).scn.

Or maybe you do but the point is that I don't.

So without further ado...

PART 8: Scenario Files

The Reading and Writing of *.scn files is controlled by two call back functions. "clbkLoadStateEx" and "clbkSaveState", I'm pretty sure you guys will figure out which one does what so let's add them to our class interface...

Code:
	// Overloaded callback functions
	void	clbkSetClassCaps (FILEHANDLE cfg);								// Set the capabilities of the vessel class
[COLOR="Red"]	
void	clbkLoadStateEx (FILEHANDLE scn, void *status);					// Read status from scenario file
	
void	clbkSaveState (FILEHANDLE scn);									// Write status to scenario file[/COLOR]	
void	clbkPostCreation ();											// Finalise vessel creation
	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

And overload them in our source file...

Code:
// --------------------------------------------------------------
// Read status from scenario file
// --------------------------------------------------------------
void LM::clbkLoadStateEx (FILEHANDLE scn, void *status)
{
	int i = 0;
	char *cbuf; 

	while (oapiReadScenario_nextline (scn, cbuf)) 
	{
		// Load default parameters
		ParseScenarioLineEx (cbuf, status);	

	} // End "while (oapiReadScenario_nextline (scn, cbuf))"

} // End "LM::clbkLoadStateEx"

// --------------------------------------------------------------
// Write status to scenario file
// --------------------------------------------------------------
void LM::clbkSaveState (FILEHANDLE scn)
{
	int i = 0;
	char cbuf[256];
	
	// Write default vessel parameters to scenario file
	VESSEL3::clbkSaveState (scn);

} // End "LM::clbkSaveState"

Unlike the other call back functions we have used so far "clbkLoadStateEx" and "clbkSaveState" need to be explicitly told what to do when called. If we did not include the calls to Orbiter's default vessel parameters our vessel would not be saved or loaded to/from the scenario file.

This is described in the Orbiter API_Guide, and the code shown above will mimic the default functionality.

Moving on...

Our first step will be to save a custom variable to our scenario. Rather than adding a vew variable to our code let's simply write our hatch's status to the scenario file

Add the following to "clbkSaveState"...

Code:
	// Write default vessel parameters to scenario file
	VESSEL3::clbkSaveState (scn);

[COLOR="red"]	// Write custom parameters to scenario file
	sprintf (cbuf, "%i %0.4f", HatchStatus, hatch_proc);	// EVA Hatch status and animation state
	oapiWriteScenario_string (scn, "HATCH", cbuf);[/COLOR]
} // End "LM::clbkSaveState"

As you can see there are two lines that we are interested in.

The first is a standard sprintf function , what it is doing is taking two numbers, an intiger corisponding to our "HatchStatus" and a Float (decimal) corisponding to "hatch_proc", and write them to the character buffer ("cbuf") declared at the top of clbkSaveState.

The next line is an Orbiter API function that actually writes a line into a scenario file.

In this case we are writing a line to the active scenario file "scn", labeling it "HATCH", and then placing the contents of "cbuf" (our current values for HatchStatus and hatch_proc) inside of it.

Let's compile and test...

Open the scenario, open your hatch, and then quit the scenario...
picture.php

NOTE: If your orbiter simulation hangs on exit, a syntax error in clbkSaveState is one of the most common culprits.

Now open (Current State).scn with a text editor and find your Lunar Lander. There should be a new line at the bottom of it's entry in the scenario.

LanderTest:UMMU_Apollo\LM
STATUS Landed Moon
POS -33.4375000 41.1184070
HEADING 100.00
AFCMODE 7
PRPLEVEL 0:1.000000 1:1.000000 2:1.000000 3:1.000000 4:1.000000 5:1.000000 6:1.000000 7:1.000000
NAVFREQ 0 0
HATCH 1 1.0000
END

Our values for HatchStatus and hatch_proc have been succesfully saved to the scenario. Now we need to load them.

Pop up to clbkLoadStateEx and add the following...

Code:
	while (oapiReadScenario_nextline (scn, cbuf)) 
	{
[COLOR="red"]		// Load animation states
        if (!_strnicmp (cbuf, "HATCH", 5)) // find line labeled "HATCH" in scn file
		{
			sscanf (cbuf+5, "%i %lf", &HatchStatus, &hatch_proc);	// Read values stored there to HatchStatus and hatch_proc
			SetAnimation (anim_Hatch, hatch_proc);					// Apply process value to animation.
		}		[/COLOR]
		// Load default parameters
		[COLOR="red"]else [/COLOR]ParseScenarioLineEx (cbuf, status);	
	} // End "while (oapiReadScenario_nextline (scn, cbuf))"

Let's break down what's happening here...

Code:
	while (oapiReadScenario_nextline (scn, cbuf)) 
	{...

Simply scans every line of the vessel's entry in the scenario.

Code:
  if (!_strnicmp (cbuf, "HATCH", 5))

Checks to see if the first 5 characters of the line being scanned are "HATCH", if not we let Orbiter's default scenario reader handle it. If they are we perform the following action...

Code:
			sscanf (cbuf+5, "%i %lf", &HatchStatus, &hatch_proc);	// Read values stored there to HatchStatus and hatch_proc
			SetAnimation (anim_Hatch, hatch_proc);					// Apply process value to animation.

the first line is another standard C++ function for parsing values. We are telling it to skip the first 5 characters of the line (the part that says "HATCH") and read the first intiger and float it can find. Those two values are then applied to HatchStatus and hatch_proc via pointer.

The second line should be self explanatory.

Compile the code and test it, your hatch's state should now persist between scenarios.

Adding additional variables is a matter of simply following the same procedure.

Code:
		// Load animation states
        if (!_strnicmp (cbuf, "HATCH", 5)) // find line labeled "HATCH" in scn file
		{
			sscanf (cbuf+5, "%i %lf", &HatchStatus, &hatch_proc);	// Read values stored there to HatchStatus and hatch_proc
			SetAnimation (anim_Hatch, hatch_proc);					// Apply process value to animation.
		}		
	[COLOR="red"]	else 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.
		}[/COLOR]
		// Load default parameters
		else ParseScenarioLineEx (cbuf, status);

Feel free to experiment with it. :tiphat:

Next up I will be talking about creating your own custom functions and vessel states with an eye towards writing a stage seperation routine.
 
Last edited:

DaveS

Addon Developer
Addon Developer
Donator
Beta Tester
Joined
Feb 4, 2008
Messages
9,429
Reaction score
680
Points
203
A couple of notes:

  • GearStatus is not declared causing the build to fail. Adding GearStatus to the enum in the header fixes this
  • LM_LG_Axis doesn't match the declaration which is LM_Leg_Axis so the build fails

Other than those which took me quite a while to find and fix properly, the code is solid. One thing though: I can't seem to get the gear to show up as retracted despite the code being good. They do move nicely and fast when triggered though.
 

Hlynkacg

Aspiring rocket scientist
Addon Developer
Tutorial Publisher
Donator
Joined
Dec 27, 2010
Messages
1,870
Reaction score
3
Points
0
Location
San Diego
A couple of notes:

  • GearStatus is not declared causing the build to fail. Adding GearStatus to the enum in the header fixes this
  • LM_LG_Axis doesn't match the declaration which is LM_Leg_Axis so the build fails

Other than those which took me quite a while to find and fix properly, the code is solid. One thing though: I can't seem to get the gear to show up as retracted despite the code being good. They do move nicely and fast when triggered though.

Odd, I will go back and try to track down the problem. Does the hatch display properly?

Part of the problem is that I'm doing a lot of copy/pasting and clean up as I go. Sometimes that gets me in trouble (I was pretty sure that GearStatus had already been declared).

I am literally going through my entire [ame="http://www.orbithangar.com/searchid.php?ID=5846"]Spider Lunar Lander *Beta v2.3*[/ame] addon, function by function, documenting and optimizing the code as I go.

My hope is that by finishing this tutorial I will be able to take the "Beta" out of the title and give newbie developers a resource that will help them avoid the numerous frustrations and false starts that I put myself through.
 

DaveS

Addon Developer
Addon Developer
Donator
Beta Tester
Joined
Feb 4, 2008
Messages
9,429
Reaction score
680
Points
203
Odd, I will go back and try to track down the problem. Does the hatch display properly?
Yes, that's the odd thing. The hatch animation work just fine. The gear starts out fully deployed at status 0 and then when you press G to trigger the animation it snaps to the retracted configuration and animates very fast to the deployed configuration, status 1.

Here's the source:

Code:
// ==============================================================
//                    ORBITER MODULE: LM
//                  Part of the ORBITER SDK
//               Copyright (C) 2012 Greg Hlynka
//                    All rights reserved
//
// LM.cpp
// Control module for LM vessel class

#include "Grumman_LM.h"

// ==============================================================
// Class Constructor and Destructor
// ==============================================================

Grumman_LM::Grumman_LM (OBJHANDLE hVessel, int flightmodel) //Constructor function
: 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;

		// Load Exterior meshes
	mh_descent		= oapiLoadMeshGlobal("SpiderLEM/LEM_DescentStage");
	mh_ascent		= oapiLoadMeshGlobal("SpiderLEM/LEM_AscentStage");
}

Grumman_LM::~Grumman_LM () //Destructor function
{
}


// ==============================================================
// Overloaded callback functions
// ==============================================================

// --------------------------------------------------------------
// Set the capabilities of the vessel class
// --------------------------------------------------------------
void Grumman_LM::clbkSetClassCaps (FILEHANDLE cfg)
{

		// Physical vessel parameters
	SetSize (LM_SIZE);
	SetEmptyMass (LM_ASC_EMPTYMASS + LM_DES_EMPTYMASS);
	SetPMI (LM_PMI);
	SetCrossSections (LM_CS);
	SetRotDrag (LM_RD);
	SetTouchdownPoints (LM_TDP[0], LM_TDP[1], LM_TDP[2]);

	// Docking port definition
	SetDockParams (LM_DOCK_POS, LM_DOCK_DIR, LM_DOCK_ROT);

	// Propellant resources
	ph_rcsA			= CreatePropellantResource (LM_RCS_FUELMASS/2);		// RCS propellant tank 'A'
	ph_rcsB			= CreatePropellantResource (LM_RCS_FUELMASS/2);		// RCS propellant tank 'B'
	ph_ascentFuel	= CreatePropellantResource (LM_ASC_FUELMASS);		// Ascent stage propellant tank
	ph_ascentO2		= CreatePropellantResource (LM_ASC_O2MASS);			// Ascent stage O2 tank
	ph_ascentH2O	= CreatePropellantResource (LM_ASC_WATERMASS);		// Ascent stage water tank

	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

	// RCS engines: Quadrant 1 (Port Fwd)
	th_rcsA[0]	= CreateThruster (LM_RCS_ENGINEPOS[0], _V( 0,-1, 0), LM_RCS_MAXTHRUST, ph_rcsA, LM_RCS_ISP*dv_mod); // Down
	th_rcsA[1]	= CreateThruster (LM_RCS_ENGINEPOS[1], _V( 0, 0,-1), LM_RCS_MAXTHRUST, ph_rcsA, LM_RCS_ISP*dv_mod); // Back
	th_rcsB[0]	= CreateThruster (LM_RCS_ENGINEPOS[0], _V( 0, 1, 0), LM_RCS_MAXTHRUST, ph_rcsB, LM_RCS_ISP*dv_mod); // Up
	th_rcsB[1]	= CreateThruster (LM_RCS_ENGINEPOS[1], _V( 1, 0, 0), LM_RCS_MAXTHRUST, ph_rcsB, LM_RCS_ISP*dv_mod); // Right

	// RCS engines: Quadrant 2 (Port Aft)
	th_rcsA[2]	= CreateThruster (LM_RCS_ENGINEPOS[2], _V( 0, 1, 0), LM_RCS_MAXTHRUST, ph_rcsA, LM_RCS_ISP*dv_mod); // Up
	th_rcsA[3]	= CreateThruster (LM_RCS_ENGINEPOS[3], _V( 0, 0, 1), LM_RCS_MAXTHRUST, ph_rcsA, LM_RCS_ISP*dv_mod); // Fwd
	th_rcsB[2]	= CreateThruster (LM_RCS_ENGINEPOS[2], _V( 0,-1, 0), LM_RCS_MAXTHRUST, ph_rcsB, LM_RCS_ISP*dv_mod); // Down 
	th_rcsB[3]	= CreateThruster (LM_RCS_ENGINEPOS[3], _V( 1, 0, 0), LM_RCS_MAXTHRUST, ph_rcsB, LM_RCS_ISP*dv_mod); // Right

	// RCS engines: Quadrant 3 (Stbd Aft)
	th_rcsA[4]	= CreateThruster (LM_RCS_ENGINEPOS[4], _V( 0,-1, 0), LM_RCS_MAXTHRUST, ph_rcsA, LM_RCS_ISP*dv_mod); // Down
	th_rcsA[5]	= CreateThruster (LM_RCS_ENGINEPOS[5], _V(-1, 0, 0), LM_RCS_MAXTHRUST, ph_rcsA, LM_RCS_ISP*dv_mod); // Left
	th_rcsB[4]	= CreateThruster (LM_RCS_ENGINEPOS[4], _V( 0, 1, 0), LM_RCS_MAXTHRUST, ph_rcsB, LM_RCS_ISP*dv_mod); // Up
	th_rcsB[5]	= CreateThruster (LM_RCS_ENGINEPOS[5], _V( 0, 0, 1), LM_RCS_MAXTHRUST, ph_rcsB, LM_RCS_ISP*dv_mod); // Fwd

	// RCS engines: Quadrant 4 (Stbd Fwd)
	th_rcsA[6]	= CreateThruster (LM_RCS_ENGINEPOS[6], _V( 0, 1, 0), LM_RCS_MAXTHRUST, ph_rcsA, LM_RCS_ISP*dv_mod); // Up
	th_rcsA[7]	= CreateThruster (LM_RCS_ENGINEPOS[7], _V(-1, 0, 0), LM_RCS_MAXTHRUST, ph_rcsA, LM_RCS_ISP*dv_mod); // Left
	th_rcsB[6]	= CreateThruster (LM_RCS_ENGINEPOS[6], _V( 0,-1, 0), LM_RCS_MAXTHRUST, ph_rcsB, LM_RCS_ISP*dv_mod); // Down
	th_rcsB[7]	= CreateThruster (LM_RCS_ENGINEPOS[7], _V( 0, 0,-1), LM_RCS_MAXTHRUST, ph_rcsB, LM_RCS_ISP*dv_mod); // Back

	// Ascent engine
	th_ascent	= CreateThruster (LM_ASC_ENGINEPOS, _V( 0, 1, 0), LM_ASC_MAXTHRUST*thrust_mod, ph_ascentFuel, LM_ASC_ISP*dv_mod);

	// Descent engine
	th_descent	= CreateThruster (LM_DES_ENGINEPOS, _V( 0, 1, 0), LM_DES_MAXTHRUST*thrust_mod, ph_descentFuel, LM_DES_ISP*dv_mod);	
	
	// Camera parameters
	SetCameraOffset (_V( 0.0, 2.1, 0.0));

	// Associate meshes for the visual
	mesh_Descent	= AddMesh (mh_descent, &LM_DES_OFFSET);
	mesh_Ascent		= AddMesh (mh_ascent, &LM_ASC_OFFSET);	

	SetMeshVisibilityMode (mesh_Descent, MESHVIS_ALWAYS);	// I want the the descent stage's legs to be visible from the cockpit

} // End "Grumman_LM::clbkSetClassCaps"

// --------------------------------------------------------------
// Read status from scenario file
// --------------------------------------------------------------
void Grumman_LM::clbkLoadStateEx (FILEHANDLE scn, void *status)
{
	int i = 0;
	char *cbuf; 

	while (oapiReadScenario_nextline (scn, cbuf)) 
	{
		// Load animation states
        if (!_strnicmp (cbuf, "HATCH", 5)) // find line labeled "HATCH" in scn file
		{
			sscanf (cbuf+5, "%i %lf", &HatchStatus, &hatch_proc);	// Read values stored there to HatchStatus and hatch_proc
			SetAnimation (anim_Hatch, hatch_proc);					// Apply process value to animation.
		}
		else 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);
		}
		// Load default parameters
		else ParseScenarioLineEx (cbuf, status);	
	} // End "while (oapiReadScenario_nextline (scn, cbuf))"

} // End "LM::clbkLoadStateEx"

// --------------------------------------------------------------
// Write status to scenario file
// --------------------------------------------------------------
void Grumman_LM::clbkSaveState (FILEHANDLE scn)
{
	int i = 0;
	char cbuf[256];
	
	// Write default vessel parameters to scenario file
	VESSEL3::clbkSaveState (scn);

	// Write custom parameters to scenario file
	sprintf (cbuf, "%i %0.4f", HatchStatus, hatch_proc);	// EVA Hatch status and animation state
	oapiWriteScenario_string (scn, "HATCH", cbuf);
	sprintf (cbuf, "%i %0.4f", GearStatus, gear_proc);	// EVA Hatch status and animation state
	oapiWriteScenario_string (scn, "GEAR", cbuf);

} // End "LM::clbkSaveState"

// --------------------------------------------------------------
// Finalize vessel creation
// --------------------------------------------------------------
void Grumman_LM::clbkPostCreation ()
{
	// EVA Hatch animation
	static UINT	meshgroup_Hatch		= AS_GRP_EvaHatch;				// participating groups
	static UINT	meshgroup_Handle	= AS_GRP_HatchHandle;

	static MGROUP_ROTATE	mgt_Hatch (mesh_Ascent, &meshgroup_Hatch, 1, _V( 0.394,-0.578, 1.661), _V( 0.0, 1.0, 0.0), (float)-90*RAD); 
	static MGROUP_ROTATE	mgt_HatchHandle (mesh_Ascent, &meshgroup_Handle, 1, _V(-0.40279,-0.55598, 1.69760), _V( 0.0, 0.0, 1.0), (float) 90*RAD);

	anim_Hatch		= CreateAnimation (0.0);
	ach_Hatch		= AddAnimationComponent (anim_Hatch, 0.3f, 1.0f, &mgt_Hatch);
	ach_HatchHandle	= AddAnimationComponent (anim_Hatch, 0.0f, 0.2f, &mgt_HatchHandle, ach_Hatch);

	// Landing Gear Animation
	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;

	anim_Gear = CreateAnimation (1.0);

	for (int i = 0; i < 4; i++)
	{
		// Animation components
		mgt_Leg[i]		= new MGROUP_ROTATE (mesh_Descent, &meshgroup_Legs[i][0], 3, LM_LegPivot[i], LM_LegAxis[i], (float) 45*RAD);		// Animate landing legs
		mgt_Strut[i]	= new MGROUP_ROTATE (mesh_Descent, &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 (mesh_Descent, &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.0, 1.0, mgt_Leg[i]);
		ach_GearStrut[i]	= AddAnimationComponent (anim_Gear, 0.0, 1.0, mgt_Strut[i], ach_GearLeg[i]);
		ach_GearLock[i]		= AddAnimationComponent (anim_Gear, 0.0, 1.0, mgt_Downlock[i], ach_GearStrut[i]);
	}

	static MGROUP_ROTATE mgt_Ladder (mesh_Descent, &meshgroup_Ladder, 1, LM_LegPivot[0], LM_LegAxis[0], (float) 45*RAD); // Apply front leg's animation state to ladder.
	AddAnimationComponent (anim_Gear, 0.0, 1, &mgt_Ladder);
} // End "Grumman_LM::clbkPostCreation"

// --------------------------------------------------------------
// Manage Animations and Post-Step processes
// --------------------------------------------------------------
void Grumman_LM::clbkPostStep (double simt, double simdt, double mjd)
{
		// EVA hatch control logic
	if (hatch_proc < 0)			// If process value is less than 0...
	{
		HatchStatus = CLOSED;	// ...set status to "CLOSED"
		hatch_proc = 0;			// and process value to 0
	}

	else if (hatch_proc > 1)	// If process value is greater than 1...
	{
		HatchStatus = OPEN;		// ...set status to "OPEN"
		hatch_proc = 1;			// and process value to 1
	}

	if (HatchStatus > CLOSED)	
	{
		double	delta = simdt / 3;	

		if (HatchStatus == OPENING)				// if Status equals "OPENING"...
		{
			hatch_proc += delta;				// ...add delta to process value
		}
		
		if (HatchStatus == CLOSING)				// if Status equals "CLOSING"...
		{
			hatch_proc -= delta;				// ...subtract it.
		}

		SetAnimation (anim_Hatch, hatch_proc);	// Apply process value to animation.
	}

	// Debuggery
	//sprintf(oapiDebugString(), "Hatch Status %0.0f, hatch_proc %0.3f", (float)HatchStatus, hatch_proc);

	// Landing Gear 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;	

		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(), "Gear Status %0.0f, gear_proc %0.3f", (float)GearStatus, gear_proc);


} // End "Grumman_LM::clbkPostStep"

// --------------------------------------------------------------
// Process keyboard inputs
// --------------------------------------------------------------
int  Grumman_LM::clbkConsumeBufferedKey (DWORD key, bool down, char *kstate)
{
		// Open hatch when [K] is pressed.
	if (key == OAPI_KEY_K  && down && !KEYMOD_SHIFT(kstate) && !KEYMOD_CONTROL (kstate) && !KEYMOD_ALT(kstate)) // [K] is down, no [shift], no [ctrl], no [alt]
	{
		if (HatchStatus == CLOSED) HatchStatus = OPENING;	// If the hatch is closed, open it
		else HatchStatus = CLOSING;							// If not, close it 
		return 1;											// return '1' to indicate that the input has been processed
	}

	// Deploy Ganding Gear when [G] is pressed.
	if (key == OAPI_KEY_G  && down && !KEYMOD_SHIFT(kstate) && !KEYMOD_CONTROL (kstate) && !KEYMOD_ALT(kstate)) // [G] is down, no [shift], no [ctrl], no [alt]
	{
		if (GearStatus == CLOSED) GearStatus = OPENING;		// If landing gear have not been deployed, deploy them
		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 Grumman_LM (hvessel, flightmodel);
}

// --------------------------------------------------------------
// Vessel cleanup
// --------------------------------------------------------------
DLLCLBK void ovcExit (VESSEL *vessel)
{
	if (vessel) delete (Grumman_LM*)vessel;
}
 

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 just tested your code and yes that is very weird.

I do not know why the hatch would work but not the gear but I have a ideas for a work-around.

PS: The gear are actually supposed to move quite fast. The actual articles were spring-loaded and took only half a second or so to deploy once the latch holding them closed released.
 

DaveS

Addon Developer
Addon Developer
Donator
Beta Tester
Joined
Feb 4, 2008
Messages
9,429
Reaction score
680
Points
203
I just tested your code and yes that is very weird.

I do not know why the hatch would work but not the gear but I have a ideas for a work-around.
Seems like there's a bug with SetAnimation for the landing gear. Gear status doesn't seem to be passed to SetAnimation so the animation is set to its default state of 1.

LM_GearStatus_AnimGear_disagreement.jpg
 
Last edited:

Hlynkacg

Aspiring rocket scientist
Addon Developer
Tutorial Publisher
Donator
Joined
Dec 27, 2010
Messages
1,870
Reaction score
3
Points
0
Location
San Diego
Seems like there's a bug with SetAnimation for the landing gear. Gear status doesn't seem to be passed to SetAnimation so the animation is set to its default state of 1.

I suspected that this was the case, but my initial idea for a workaround failed so now I'm experimenting.

For the moment I'm going to chalk it up as a "known issue" and start pushing forward on custom vessel states and staging.

---------- Post added at 13:50 ---------- Previous post was at 12:15 ----------

PART 8: Custom Functions and Sub-routines

This is a subject that often intimidates newbies but once you realise just how easy it is to do there really is no going back.

Thus far we have been overloading functions orbiter's core functions. These functions all existed in Orbiter prior to our calling them. Now it's time to start creating entirely new functions. We'll start with something easy.

Go to your class interface in your header file and add the following lines.

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

[COLOR="Red"]	// Custom vessel functions
	void	DefineAnimations (void);	[/COLOR]		
	
	// 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

We have now declared a new (currently non-existant) function that will be unique to our vessel class.

It's keyword is "void" because, clbkConsumeBufferedKey, we don't expect or need it to return a value. It's input ( in perenthesis) ;) is also "void" because it's instructions will not be dependant on any external function or variables.

Now that the function has been declared we need to define it. Let's go back to our source file and add a new section to it below our destructor but above our overloaded callback functions.

Code:
LM::~LM ()
{
}

[COLOR="red"]// ==============================================================
// Custom Vessel Functions
// ==============================================================

// --------------------------------------------------------------
// Define meshgroup transformations
// --------------------------------------------------------------
void LM::DefineAnimations (void)
{

}[/COLOR]

// ==============================================================
// Overloaded callback functions
// ==============================================================

Our function is now a "stub", meaning that it has been properly declared/defined but it doesn't do anything yet.

Let's change that. Go down and cut/paste all of our animation components from clbkPostCreation to our new animation function.

Code:
// --------------------------------------------------------------
// Define mesh animations
// --------------------------------------------------------------
void LM::DefineAnimations (void)
{
[COLOR="red"]	// EVA Hatch animation
	static UINT	meshgroup_Hatch		= AS_GRP_EvaHatch;				// participating groups
	static UINT	meshgroup_Handle	= AS_GRP_HatchHandle;

	static MGROUP_ROTATE	mgt_Hatch (mesh_Ascent, &meshgroup_Hatch, 1, _V( 0.394,-0.578, 1.661), _V( 0.0, 1.0, 0.0), (float)-90*RAD); 
	static MGROUP_ROTATE	mgt_HatchHandle (mesh_Ascent, &meshgroup_Handle, 1, _V(-0.40279,-0.55598, 1.69760), _V( 0.0, 0.0, 1.0), (float) 90*RAD);

	anim_Hatch		= CreateAnimation (0.0);
	ach_Hatch		= AddAnimationComponent (anim_Hatch, 0.3f, 1.0f, &mgt_Hatch);
	ach_HatchHandle	= AddAnimationComponent (anim_Hatch, 0.0f, 0.2f, &mgt_HatchHandle, ach_Hatch);

	// Landing Gear Animation
	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;

	anim_Gear = CreateAnimation (1.0);

	for (int i = 0; i < 4; i++)
	{
		// Animation components
		mgt_Leg[i]		= new MGROUP_ROTATE (mesh_Descent, &meshgroup_Legs[i][0], 3, LM_LegPivot[i], LM_LegAxis[i], (float) 45*RAD);		// Animate landing legs
		mgt_Strut[i]	= new MGROUP_ROTATE (mesh_Descent, &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 (mesh_Descent, &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.0, 1.0, mgt_Leg[i]);
		ach_GearStrut[i]	= AddAnimationComponent (anim_Gear, 0.0, 1.0, mgt_Strut[i], ach_GearLeg[i]);
		ach_GearLock[i]		= AddAnimationComponent (anim_Gear, 0.0, 1.0, mgt_Downlock[i], ach_GearStrut[i]);
	}

	static MGROUP_ROTATE mgt_Ladder (mesh_Descent, &meshgroup_Ladder, 1, LM_LegPivot[0], LM_LegAxis[0], (float) 45*RAD); // Apply front leg's animation state to ladder.
	AddAnimationComponent (anim_Gear, 0.0, 1, &mgt_Ladder);[/COLOR]}
Then in clbkPostCreation add a call to "DefineAnimations"

Code:
// --------------------------------------------------------------
// Finalise vessel creation
// --------------------------------------------------------------
void LM::clbkPostCreation ()
{
	[COLOR="red"]DefineAnimations ();[/COLOR]

} // End "LM::clbkPostCreation"

Compile your code and test it.

Functionally speaking you shouldn't notice anything different but under the hood this represents a signifigant change. It may not seem like much now but this will save us huge amounts of time and effort down the line.

In fact, lets plan ahead and add a few more stubs to our source file...

Code:
	static MGROUP_ROTATE mgt_Ladder (mesh_Descent, &meshgroup_Ladder, 1, LM_LegPivot[0], LM_LegAxis[0], (float) 45*RAD); // Apply front leg's animation state to ladder.
	AddAnimationComponent (anim_Gear, 0.0, 1, &mgt_Ladder);

} // End "LM::DefineAnimations"

[COLOR="red"]// --------------------------------------------------------------
// Define local light sources
// --------------------------------------------------------------
void LM::DefineLighting (void)
{

} // End "LM::DefineLighting"

// --------------------------------------------------------------
// Define exhaust and particle streams
// --------------------------------------------------------------
void LM::DefineParticleStreams (void)
{

} // End "LM::DefineParticleStreams"

// --------------------------------------------------------------
// Define sound effects
// --------------------------------------------------------------
void LM::DefineSounds (void)
{

} // End "LM::DefineSounds"[/COLOR]

Declare them in the header file the same way we declared "DefineAnimations" and add them to clbkPostCreation.

Code:
// --------------------------------------------------------------
// Finalise vessel creation
// --------------------------------------------------------------
void LM::clbkPostCreation ()
{
	DefineAnimations ();
[COLOR="red"]	DefineLighting ();
	DefineParticleStreams ();
	DefineSounds ();[/COLOR]

} // End "LM::clbkPostCreation"

Witness the power of this fully armed and operational object-oriented programming language!

The ability to break large blocks of our vessel's code into seperate sub-functions will be incredibly important moving forward.

---------- Post added at 13:58 ---------- Previous post was at 13:50 ----------

CURRENT STATE OF HEADER AND SOURCE

Header...
Code:
// ==============================================================
//                    ORBITER MODULE: LM
//                  Part of the ORBITER SDK
//               Copyright (C) 2012 Greg Hlynka
//                    All rights reserved
//
// LM.h
// Interface for LM vessel class
//
// Notes: This file exists
// ==============================================================

#define STRICT
#define ORBITER_MODULE

// Orbiter SDK files
#include "orbitersdk.h"

// Mesh resource files
#include "_ascentmesh.h"
#include "_descentmesh.h"
#include "_cockpitmesh.h"

// ==============================================================
// Vessel parameters
// ==============================================================

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
const double	LM_SIZE				= 5.5;					// Vessel's mean radius [m]
const double	LM_ASC_SIZE			= 2.5;					// Mean radius [m] after staging
const VECTOR3	LM_CS				= { 19.0, 19.2, 18.8};	// x,y,z cross sections [m^2]
const VECTOR3	LM_ASC_CS			= { 9.62, 12.6, 9.25};	// Cross sections [m^2] after staging
const VECTOR3	LM_PMI				= { 2.68, 2.10, 2.64};	// Principal moments of inertia (mass-normalised) [m^2]
const VECTOR3	LM_ASC_PMI			= {	1.31, 1.68, 1.25};	// Principal moments of inertia (mass-normalised) [m^2] after staging
const VECTOR3	LM_RD				= {	0.04, 0.05, 0.04};	// Rotation drag coefficients
const VECTOR3	LM_ASC_RD			= {	0.03, 0.07, 0.09};	// Rotation drag coefficients after staging
const double	LM_ASC_EMPTYMASS	= 1922.78;				// Ascent stage empty mass [kg] (4239 lbs, page 119)
const double	LM_DES_EMPTYMASS	= 1798.95;				// Descent stage empty mass [kg] (3,966 lbs, page 119)
const double	LM_ASC_FUELMASS		= 2353.0;				// Ascent stage fuel mass [kg] 
const double	LM_DES_FUELMASS		= 8480.81;				// Descent stage fuel mass [kg] (18,697 lbs, LMFM pg 58)
const double	LM_RCS_FUELMASS		= 750.0;				// RCS fuel mass [kg] (633 lbs, LMFM pg 72)
const double	LM_ASC_WATERMASS	= 19.55;				// max fuel mass [kg] (page 177)
const double	LM_DES_WATERMASS	= 135.64;				// Descent stage water storage mass [kg] (299 lbs, page 177)
const double	LM_ASC_O2MASS		= 1.102;				// max fuel mass [kg] (2.43 lbs)
const double	LM_DES_O2MASS		= 20.50;				// Descent stage O2 reserve mass [kg] (45.2 lbs)
const double	LM_ASC_ISP			= 3050.91;				// Ascent stage specific impulse [m/s] (311 sec, Astronautix.com)
const double	LM_DES_ISP			= 3075.44;				// Descent stage specific impulse [m/s] (313.5 sec, Astronautix.com)
const double	LM_RCS_ISP			= 2844.90;				// RCS specific impulse [m/s] (290 sec, Astronautix.com)
const VECTOR3	LM_TDP[3]			= {{ 0.0,-3.1, 4.4}, {-3.8,-3.1,-2.2}, { 3.8,-3.1,-2.2}};	// touchdown points [m]
const VECTOR3	LM_ASC_TDP[3]		= {{ 0.0,-1.4, 1.7}, {-0.9,-1.4,-1.5}, { 0.9,-1.4,-1.5}};	// touchdown points [m] after staging
const double	LM_ASC_MAXTHRUST	= 15568.8;				// Ascent stage max thrust [n] (3,500 lbf, LMFM pg 58)
const double	LM_DES_MAXTHRUST	= 44482.2;				// Descent stage max thrust	[n] (10,000 lbf, LMFM pg 58)
const double	LM_RCS_MAXTHRUST	= 440.0;				// RCS thruster max thrust [n] (100 lbf)
const VECTOR3	LM_ASC_ENGINEPOS	= {	0.00,-0.64, 0.00};	// Ascent stage engine position [m]
const VECTOR3	LM_DES_ENGINEPOS	= { 0.00,-1.18, 0.00};	// Descent stage  engine position [m]
const VECTOR3	LM_RCS_ENGINEPOS[8]	= {{-1.68, 1.44, 1.68}, {-1.562, 1.44, 1.562}, {-1.68, 1.44,-1.68}, {-1.562, 1.44,-1.562}, { 1.68, 1.44,-1.68}, { 1.562, 1.44,-1.562}, { 1.68, 1.44, 1.68}, { 1.562, 1.44, 1.562}}; // RCS engine positions [m]

const VECTOR3	LM_DOCK_POS			= { 0.00, 2.93, 0.00};	// docking port location [m]
const VECTOR3	LM_DOCK_DIR			= { 0, 1, 0};			// docking port approach direction
const VECTOR3	LM_DOCK_ROT			= { 0, 0, 1};			// docking port alignment direction

// ==============================================================
// Lunar Module class interface
// ==============================================================

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

	// Custom vessel functions
	void	DefineAnimations (void);			// Define mesh animations
	void	DefineLighting (void);				// Define local light sources
	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	clbkPostCreation ();											// Finalise vessel creation
	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_rcsA, ph_rcsB, ph_ascentFuel, ph_descentFuel;			// Functional Propellant tanks
	PROPELLANT_HANDLE	ph_ascentO2, ph_ascentH2O, ph_descentO2, ph_descentH2O;		// Represenative propellant tanks (consumables)
	
	THRUSTER_HANDLE		th_rcsA[8], th_rcsB[8], th_ascent, th_descent;				// Functional thrusters
	THRUSTER_HANDLE		th_dust, th_vent;											// Represenative thrusters (particle streams)

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;

	// Animations
	UINT						anim_Hatch, anim_Gear;
	double						hatch_proc, gear_proc;
	ANIMATIONCOMPONENT_HANDLE	ach_GearLeg[4], ach_GearStrut[4], ach_GearLock[4], ach_Hatch, ach_HatchHandle;
	MGROUP_TRANSFORM			*mgt_Leg[4], *mgt_Strut[4], *mgt_Downlock[4];
	
	// Meshes
	MESHHANDLE	mh_descent, mh_ascent;	// Mesh handles
	UINT		mesh_Descent;			// Descent stage mesh index
	UINT		mesh_Ascent;			// Ascent stage mesh index

}; // End "class LM:"

Source...
Code:
// ==============================================================
//                    ORBITER MODULE: LM
//                  Part of the ORBITER SDK
//               Copyright (C) 2012 Greg Hlynka
//                    All rights reserved
//
// LM.cpp
// Control module for LM vessel class
//
// Notes: Writing Tutorials is hard work
// ==============================================================

#include "LM.h"

// ==============================================================
// Class Constructor and Destructor
// ==============================================================

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;

	// Initial vessel state
	VesselStatus	= DESCENT;

	// Load Exterior meshes
	mh_descent		= oapiLoadMeshGlobal("UMMU_Apollo/LEM_DescentStage");
	mh_ascent		= oapiLoadMeshGlobal("UMMU_Apollo/LEM_AscentStage");
}

LM::~LM ()
{
}

// ==============================================================
// Custom Vessel Functions
// ==============================================================

// --------------------------------------------------------------
// Define mesh animations
// --------------------------------------------------------------
void LM::DefineAnimations (void)
{
	// EVA Hatch animation
	static UINT	meshgroup_Hatch		= AS_GRP_EvaHatch;				// participating groups
	static UINT	meshgroup_Handle	= AS_GRP_HatchHandle;

	static MGROUP_ROTATE	mgt_Hatch (mesh_Ascent, &meshgroup_Hatch, 1, _V( 0.394,-0.578, 1.661), _V( 0.0, 1.0, 0.0), (float)-90*RAD); 
	static MGROUP_ROTATE	mgt_HatchHandle (mesh_Ascent, &meshgroup_Handle, 1, _V(-0.40279,-0.55598, 1.69760), _V( 0.0, 0.0, 1.0), (float) 90*RAD);

	anim_Hatch		= CreateAnimation (0.0);
	ach_Hatch		= AddAnimationComponent (anim_Hatch, 0.3f, 1.0f, &mgt_Hatch);
	ach_HatchHandle	= AddAnimationComponent (anim_Hatch, 0.0f, 0.2f, &mgt_HatchHandle, ach_Hatch);

	// Landing Gear Animation
	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;

	anim_Gear = CreateAnimation (1.0);

	for (int i = 0; i < 4; i++)
	{
		// Animation components
		mgt_Leg[i]		= new MGROUP_ROTATE (mesh_Descent, &meshgroup_Legs[i][0], 3, LM_LegPivot[i], LM_LegAxis[i], (float) 45*RAD);		// Animate landing legs
		mgt_Strut[i]	= new MGROUP_ROTATE (mesh_Descent, &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 (mesh_Descent, &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.0, 1.0, mgt_Leg[i]);
		ach_GearStrut[i]	= AddAnimationComponent (anim_Gear, 0.0, 1.0, mgt_Strut[i], ach_GearLeg[i]);
		ach_GearLock[i]		= AddAnimationComponent (anim_Gear, 0.0, 1.0, mgt_Downlock[i], ach_GearStrut[i]);
	}

	static MGROUP_ROTATE mgt_Ladder (mesh_Descent, &meshgroup_Ladder, 1, LM_LegPivot[0], LM_LegAxis[0], (float) 45*RAD); // Apply front leg's animation state to ladder.
	AddAnimationComponent (anim_Gear, 0.0, 1, &mgt_Ladder);

} // End "LM::DefineAnimations"

// --------------------------------------------------------------
// Define local light sources
// --------------------------------------------------------------
void LM::DefineLighting (void)
{

} // End "LM::DefineLighting"

// --------------------------------------------------------------
// Define exhaust and particle streams
// --------------------------------------------------------------
void LM::DefineParticleStreams (void)
{

} // End "LM::DefineParticleStreams"

// --------------------------------------------------------------
// Define sound effects
// --------------------------------------------------------------
void LM::DefineSounds (void)
{

} // End "LM::DefineSounds"

// ==============================================================
// Overloaded callback functions
// ==============================================================

// --------------------------------------------------------------
// Set the capabilities of the vessel class
// --------------------------------------------------------------
void LM::clbkSetClassCaps (FILEHANDLE cfg)
{
	// Read dificulty modifiers from config file
	oapiReadItem_float (cfg, "THRUST_MODIFIER", thrust_mod);			// Max thrust multiplier 
	oapiReadItem_float (cfg, "DV_MODIFIER", dv_mod);					// Exhaust velocity multiplier
	oapiReadItem_float (cfg, "CONSUMABLES_MODIFIER", consumables_mod);	// Consumables consumption rate (1 = 100% of historical rate)

	// Physical vessel parameters
	SetSize (LM_SIZE);
	SetEmptyMass (LM_ASC_EMPTYMASS + LM_DES_EMPTYMASS);
	SetPMI (LM_PMI);
	SetCrossSections (LM_CS);
	SetRotDrag (LM_RD);
	SetTouchdownPoints (LM_TDP[0], LM_TDP[1], LM_TDP[2]);

	// Docking port definition
	SetDockParams (LM_DOCK_POS, LM_DOCK_DIR, LM_DOCK_ROT);
	
	// Attachment point definition
	CreateAttachment (true, _V( 0.0, 0.4, 0.0), _V( 0, 1, 0), _V( 0, 0, 1), "X");	// for attachment to launch vehicle

	// Propellant resources
	ph_rcsA			= CreatePropellantResource (LM_RCS_FUELMASS/2);		// RCS propellant tank 'A'
	ph_rcsB			= CreatePropellantResource (LM_RCS_FUELMASS/2);		// RCS propellant tank 'B'
	ph_ascentFuel	= CreatePropellantResource (LM_ASC_FUELMASS);		// Ascent stage propellant tank
	ph_ascentO2		= CreatePropellantResource (LM_ASC_O2MASS);			// Ascent stage O2 tank
	ph_ascentH2O	= CreatePropellantResource (LM_ASC_WATERMASS);		// Ascent stage water tank

	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

	// RCS engines: Quadrant 1 (Port Fwd)
	th_rcsA[0]	= CreateThruster (LM_RCS_ENGINEPOS[0], _V( 0,-1, 0), LM_RCS_MAXTHRUST, ph_rcsA, LM_RCS_ISP*dv_mod); // Down
	th_rcsA[1]	= CreateThruster (LM_RCS_ENGINEPOS[1], _V( 0, 0,-1), LM_RCS_MAXTHRUST, ph_rcsA, LM_RCS_ISP*dv_mod); // Back
	th_rcsB[0]	= CreateThruster (LM_RCS_ENGINEPOS[0], _V( 0, 1, 0), LM_RCS_MAXTHRUST, ph_rcsB, LM_RCS_ISP*dv_mod); // Up
	th_rcsB[1]	= CreateThruster (LM_RCS_ENGINEPOS[1], _V( 1, 0, 0), LM_RCS_MAXTHRUST, ph_rcsB, LM_RCS_ISP*dv_mod); // Right

	// RCS engines: Quadrant 2 (Port Aft)
	th_rcsA[2]	= CreateThruster (LM_RCS_ENGINEPOS[2], _V( 0, 1, 0), LM_RCS_MAXTHRUST, ph_rcsA, LM_RCS_ISP*dv_mod); // Up
	th_rcsA[3]	= CreateThruster (LM_RCS_ENGINEPOS[3], _V( 0, 0, 1), LM_RCS_MAXTHRUST, ph_rcsA, LM_RCS_ISP*dv_mod); // Fwd
	th_rcsB[2]	= CreateThruster (LM_RCS_ENGINEPOS[2], _V( 0,-1, 0), LM_RCS_MAXTHRUST, ph_rcsB, LM_RCS_ISP*dv_mod); // Down 
	th_rcsB[3]	= CreateThruster (LM_RCS_ENGINEPOS[3], _V( 1, 0, 0), LM_RCS_MAXTHRUST, ph_rcsB, LM_RCS_ISP*dv_mod); // Right

	// RCS engines: Quadrant 3 (Stbd Aft)
	th_rcsA[4]	= CreateThruster (LM_RCS_ENGINEPOS[4], _V( 0,-1, 0), LM_RCS_MAXTHRUST, ph_rcsA, LM_RCS_ISP*dv_mod); // Down
	th_rcsA[5]	= CreateThruster (LM_RCS_ENGINEPOS[5], _V(-1, 0, 0), LM_RCS_MAXTHRUST, ph_rcsA, LM_RCS_ISP*dv_mod); // Left
	th_rcsB[4]	= CreateThruster (LM_RCS_ENGINEPOS[4], _V( 0, 1, 0), LM_RCS_MAXTHRUST, ph_rcsB, LM_RCS_ISP*dv_mod); // Up
	th_rcsB[5]	= CreateThruster (LM_RCS_ENGINEPOS[5], _V( 0, 0, 1), LM_RCS_MAXTHRUST, ph_rcsB, LM_RCS_ISP*dv_mod); // Fwd

	// RCS engines: Quadrant 4 (Stbd Fwd)
	th_rcsA[6]	= CreateThruster (LM_RCS_ENGINEPOS[6], _V( 0, 1, 0), LM_RCS_MAXTHRUST, ph_rcsA, LM_RCS_ISP*dv_mod); // Up
	th_rcsA[7]	= CreateThruster (LM_RCS_ENGINEPOS[7], _V(-1, 0, 0), LM_RCS_MAXTHRUST, ph_rcsA, LM_RCS_ISP*dv_mod); // Left
	th_rcsB[6]	= CreateThruster (LM_RCS_ENGINEPOS[6], _V( 0,-1, 0), LM_RCS_MAXTHRUST, ph_rcsB, LM_RCS_ISP*dv_mod); // Down
	th_rcsB[7]	= CreateThruster (LM_RCS_ENGINEPOS[7], _V( 0, 0,-1), LM_RCS_MAXTHRUST, ph_rcsB, LM_RCS_ISP*dv_mod); // Back

	// Ascent engine
	th_ascent	= CreateThruster (LM_ASC_ENGINEPOS, _V( 0, 1, 0), LM_ASC_MAXTHRUST*thrust_mod, ph_ascentFuel, LM_ASC_ISP*dv_mod);

	// Descent engine
	th_descent	= CreateThruster (LM_DES_ENGINEPOS, _V( 0, 1, 0), LM_DES_MAXTHRUST*thrust_mod, ph_descentFuel, LM_DES_ISP*dv_mod);	
	
	// Camera parameters
	SetCameraOffset (_V( 0.0, 2.1, 0.0));

	// Associate meshes for the visual
	mesh_Descent	= AddMesh (mh_descent, &LM_DES_OFFSET);	// Descent stage mesh
	mesh_Ascent		= AddMesh (mh_ascent, &LM_ASC_OFFSET);	// Ascent stage mesh

	SetMeshVisibilityMode (mesh_Descent, MESHVIS_ALWAYS);	// I want the the descent stage's legs to be visible from the cockpit, so I've set the mesh_Descent to be always visible. By default a vessel's own meshes are only visible when in an external view.
} // End "LM::clbkSetClassCaps"

// --------------------------------------------------------------
// Read status from scenario file
// --------------------------------------------------------------
void LM::clbkLoadStateEx (FILEHANDLE scn, void *status)
{
	int i = 0;
	char *cbuf; 

	while (oapiReadScenario_nextline (scn, cbuf)) 
	{
		// Load Vessel State
		if (!_strnicmp( cbuf, "STATE", 5))  // find line labeled "STATE" in scn file
		{
			sscanf(cbuf+5, "%i", &VesselStatus);	// Read value stored there to VesselStatus
		}

		// Load animation states
        else if (!_strnicmp (cbuf, "HATCH", 5)) // find line labeled "HATCH" in scn file
		{
			sscanf (cbuf+5, "%i %lf", &HatchStatus, &hatch_proc);	// Read values stored there to HatchStatus and hatch_proc
			SetAnimation (anim_Hatch, hatch_proc);					// Apply process value to animation.
		}		
		else 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 "LM::clbkLoadStateEx"

// --------------------------------------------------------------
// Write status to scenario file
// --------------------------------------------------------------
void LM::clbkSaveState (FILEHANDLE scn)
{
	int i = 0;
	char cbuf[256];
	
	// Write default vessel parameters to scenario file
	VESSEL3::clbkSaveState (scn);

	// Write custom parameters to scenario file
	sprintf (cbuf, "%i", VesselStatus);						// Vessel status
	oapiWriteScenario_string (scn, "STATE", cbuf);

	sprintf (cbuf, "%i %0.4f", HatchStatus, hatch_proc);	// EVA Hatch status and animation state
	oapiWriteScenario_string (scn, "HATCH", cbuf);

	sprintf (cbuf, "%i %0.4f", GearStatus, gear_proc);		// Landing Gear status and animation state
	oapiWriteScenario_string (scn, "GEAR", cbuf);

} // End "LM::clbkSaveState"

// --------------------------------------------------------------
// Finalise vessel creation
// --------------------------------------------------------------
void LM::clbkPostCreation ()
{
	DefineAnimations ();
	DefineLighting ();
	DefineParticleStreams ();
	DefineSounds ();

} // End "LM::clbkPostCreation"

// --------------------------------------------------------------
// Manage Animations and Post-Step processes
// --------------------------------------------------------------
void LM::clbkPostStep (double simt, double simdt, double mjd)
{
	// Debuggery
	//sprintf (oapiDebugString(), "Vessel Status %0.0f", (float)VesselStatus);

	// EVA hatch control logic
	if (hatch_proc < 0)			// If process value is less than 0...
	{
		HatchStatus = CLOSED;	// ...set status to "CLOSED"
		hatch_proc = 0;			// and process value to 0
	}

	else if (hatch_proc > 1)	// If process value is greater than 1...
	{
		HatchStatus = OPEN;		// ...set status to "OPEN"
		hatch_proc = 1;			// and process value to 1
	}

	if (HatchStatus > CLOSED)	
	{
		double	delta = simdt / 3;	

		if (HatchStatus == OPENING)				// if Status equals "OPENING"...
		{
			hatch_proc += delta;				// ...add delta to process value
		}
		
		if (HatchStatus == CLOSING)				// if Status equals "CLOSING"...
		{
			hatch_proc -= delta;				// ...subtract it.
		}

		SetAnimation (anim_Hatch, hatch_proc);	// Apply process value to animation.
	}

	// Debuggery
	//sprintf (oapiDebugString (), "Hatch Status %0.0f, hatch_proc %0.3f", (float)HatchStatus, hatch_proc);

	// Landing Gear 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 (), "Gear Status %0.0f, gear_proc %0.3f", (float)GearStatus, gear_proc);

} // End "LM::clbkPostStep"

// --------------------------------------------------------------
// Process keyboard inputs
// --------------------------------------------------------------
int  LM::clbkConsumeBufferedKey (DWORD key, bool down, char *kstate)
{
	// Open hatch when [K] is pressed.
	if (key == OAPI_KEY_K  && down && !KEYMOD_SHIFT(kstate) && !KEYMOD_CONTROL (kstate) && !KEYMOD_ALT(kstate)) // [K] is down, no [shift], no [ctrl], no [alt]
	{
		if (HatchStatus == CLOSED) HatchStatus = OPENING;	// If the hatch is closed, open it
		else HatchStatus = CLOSING;							// If not, close it 
		return 1;											// return '1' to indicate that the input has been processed
	}

	// Deploy Ganding Gear when [G] is pressed.
	if (key == OAPI_KEY_G  && down && !KEYMOD_SHIFT(kstate) && !KEYMOD_CONTROL (kstate) && !KEYMOD_ALT(kstate)) // [K] is down, no [shift], no [ctrl], no [alt]
	{
		if (GearStatus != OPEN) GearStatus = OPENING;		// If landing gear have not been deployed, deploy them
		return 1;
	}

	return 0;	// if no keys are pressed the function returns '0' and nothing happens

} // End "LM::clbkConsumeBufferedKey"

// ==============================================================
// API callback interface
// ==============================================================

// --------------------------------------------------------------
// Vessel initialisation
// --------------------------------------------------------------
DLLCLBK VESSEL *ovcInit (OBJHANDLE hvessel, int flightmodel)
{
	return new LM (hvessel, flightmodel);
}

// --------------------------------------------------------------
// Vessel cleanup
// --------------------------------------------------------------
DLLCLBK void ovcExit (VESSEL *vessel)
{
	if (vessel) delete (LM*)vessel;
}

NOTE: VesselStatus enumerator will be subject of part 10.
 
Last edited:

Hlynkacg

Aspiring rocket scientist
Addon Developer
Tutorial Publisher
Donator
Joined
Dec 27, 2010
Messages
1,870
Reaction score
3
Points
0
Location
San Diego
A note before we proceed...

Ordinarily Orbiter does not track a vessel's physical capabilities and attributes on a frame-by-frame basis. It simply takes those declared in clbkSetClassCaps and applies them.

Because our vessel's configuration is going to change over the course of the simulation (most notably at stage seperation) we need to write a function (or rather a set of functions) that will track and update our vessel's attributes. To facilitate this I have added an additional enumerator to our class interface describing our lander's major phases of flight.

Code:
enum	vesselstate	{LAUNCH, DESCENT, ASCENT, WRECKED}	VesselStatus;

The "LAUNCH" phase is essentially a "safe mode" meant to be used when the lander is powered down and attached to it's booster/SLA. "DESCENT" will be our normal/default flight configuration. And the ASCENT will be our configuration after the Descent Stage has been jettisoned.

I also added a fourth element, "WRECKED", as a placeholder should I decide to add a damage/destruction model to the vessel somewhere down the line.

While in essance the Lunar Lander is a simple 2 stage rocket (that has the slight quirk of staging while landed) the basic principals behind the functions that we are about to discuss can just as easily be applied to something more complex, such as a launch vehicle.

For instance, if I were to be coding a Saturn 5 my enumerator might look something like this...

Code:
enum	boosterstate	{ONPAD, STAGE1BURN, STAGE2BURN, STAGE3BURN, INORBIT}	BoosterStatus;

;)

---------- Post added at 15:54 ---------- Previous post was at 14:23 ----------

PART 9: Custom Vessel States
First things first. Let's add the VesselStatus enumerator to our class interface and initialize it in the constructor.

Code:
private:
	// Vessel status variables
[COLOR="Red"]	enum	vesselstate	{LAUNCH, DESCENT, ASCENT, WRECKED}	VesselStatus;[/COLOR]	
	enum	doorstate	{CLOSED, OPEN, CLOSING, OPENING}	HatchStatus, GearStatus;

Code:
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;

	// Initial vessel state
[COLOR="red"]	VesselStatus	= DESCENT;[/COLOR]

Intializing it in this manner makes "DESCENT" the default starting state, so that, in the absence of other instructions, orbiter will spawn our lander with the decent stage attached and ready for "normal" flight.

Let's also add a VesselStatus line to our clbkLoadState and clbkSaveState functions.

Code:
	while (oapiReadScenario_nextline (scn, cbuf)) 
	{
		[COLOR="red"]// Load Vessel State
		if (!_strnicmp( cbuf, "STATE", 5))  // find line labeled "STATE" in scn file
		{
			sscanf(cbuf+5, "%i", &VesselStatus);	// Read value stored there to VesselStatus
		}[/COLOR]

		// Load animation states
        [COLOR="red"]else[/COLOR] if (!_strnicmp (cbuf, "HATCH", 5)) // find line labeled "HATCH" in scn file
		{

Code:
	// Write default vessel parameters to scenario file
	VESSEL3::clbkSaveState (scn);

	// Write custom parameters to scenario file
	[COLOR="red"]sprintf (cbuf, "%i", VesselStatus);						// Vessel status
	oapiWriteScenario_string (scn, "STATE", cbuf);[/COLOR]

	sprintf (cbuf, "%i %0.4f", HatchStatus, hatch_proc);	// EVA Hatch status and animation state
	oapiWriteScenario_string (scn, "HATCH", cbuf);

With this little bit of house-keeping out of the way, lets go back to our class interface and declare a new custom function.

The first parameter that we are going to be tracking/updating is our vessel's empty mass so let's call our function "CalcEmptyMass".

Code:
	// Custom vessel functions
	void	DefineAnimations (void);			// Define mesh animations
	void	DefineLighting (void);				// Define local light sources
	void	DefineParticleStreams (void);		// Define exhaust and particle streams
	void	DefineSounds (void);				// Define sound effects
[COLOR="red"]	double	CalcEmptyMass (void);				// Calculate current empty mass of vessel[/COLOR]

Note that unlike our previous functions we will be wanting "CalcEmptyMass" to return a value. As such we must assign it a data-type. Orbiter uses "double" for mass calculation therefore that is the data-type that we will use.

Now that the function has been declared let's go to the source file and define it.

Code:
// --------------------------------------------------------------
// Calculate current empty mass of vessel
// --------------------------------------------------------------
double LM::CalcEmptyMass (void)
{
	double output;

	if (VesselStatus == ASCENT) output = LM_ASC_EMPTYMASS;	// If vessel is in ascent configuration output equals ascent stage empty mass. 
	else output = LM_ASC_EMPTYMASS + LM_DES_EMPTYMASS;		// If not, output equals the empty masses of the ascent and descent stages combined. (Default empty mass)

	// Add mass of crew	**not implimented**
	// Add mass of cargo	**not implimented**

	return output;	// Return total
}

The function really is quite simple. If the lander is in ascent configuration our output value should be equal to the ascent stage's empty mass. If not it should be equal to the ascent stage's empty mass plus that of the descent stage.

NOTE: Orbiter counts anything that isn't in a fuel tank as "Empty Mass". Our vessel currently has no crew or cargo management, but if/when they are added they will need to accounted for in this calculation as well, hense the two place holders.

Now that we have a function where do we call it? Ideally we want orbiter to check/update our vessel's physical state at the beginning of each time step (before any forces are applied) and it just so happens that there is a call back function for doing exactly that. It is called "clbkPreStep".

Declare it in your class interface...

Code:
	void	clbkPostCreation ();											// Finalise vessel creation
	[COLOR="red"]void	clbkPreStep (double simt, double simdt, double mjd);			// Pre-Step processes[/COLOR]
	void	clbkPostStep (double simt, double simdt, double mjd);			// Post-Step processes

And overload it...

Code:
} // End "LM::clbkPostCreation"

[COLOR="red"]// --------------------------------------------------------------
// Pre-Step processes
// --------------------------------------------------------------
void LM::clbkPreStep (double simt, double simdt, double mjd)
{
	// Update Vessel State
	SetEmptyMass (CalcEmptyMass ());

	// Debuggery
	sprintf (oapiDebugString(), "Vessel Status %0.0f, Empty Mass %0.3f", (float)VesselStatus, GetMass());
} // End "LM::clbkPreStep"[/COLOR]

// --------------------------------------------------------------
// Post-Step processes
// --------------------------------------------------------------
void LM::clbkPostStep (double simt, double simdt, double mjd)
{

The meaning of "SetEmptyMass (CalcEmptyMass ());" should be obvious. We are telling orbiter to set our vessel's empty mass to the value returned by our "CalcEmptyMass ()" function.

Compile and test...

Everything should function normally but if you go into the scenario file and change your vessel's status to "ASCENT" (STATE 2) you should note a change in your vessel's mass.

Congratulations, we are on our way. :tiphat:

---------- Post added at 17:12 ---------- Previous post was at 15:54 ----------

PART 9: Custom Vessel States (Continued)

Now to control the rest of or vessel's parameters I am going to introduce a new type of statement called a "switch". Switches are broken into a set set of "cases" that function similar to a set of nested "if" functions.

If not "case 1" then "case 2", if not "case 2" then "case 3", and so on down the line. Specific outputs or functions can then be assigned to a given case.

The following is an example of a simple switch statement
Code:
switch (x)
{
 case x < 0:
  **x is a negative number**
  break;

 case x = 0:
  **x is equal to 0**
  break;

 case x > 0:
  **x is a positive number**
  break;
}

NOTE: cases must be unambiguous (no overlap).

What we are going to do is create a switch that takes "VesselStatus" as it's input and then uses the individual states as it's cases. Go to clbkPreStep and add the following.

Code:
	// Update Vessel State
	SetEmptyMass (CalcEmptyMass ());

[COLOR="Red"]	switch (VesselStatus)
	{
	case LAUNCH:
		
		break;

	case DESCENT:

		break;

	case ASCENT:

		break;
	} // End "switch (VesselStatus)"[/COLOR]

So far so good. Now let's add some simple instructions...

Code:
	switch (VesselStatus)
	{
	case LAUNCH:
[COLOR="red"]		GearStatus = CLOSED;
		gear_proc = 0;
		SetAnimation( anim_Gear, 0);[/COLOR]
		break;

	case DESCENT:
		[COLOR="red"]SetDefaultPropellantResource (ph_descentFuel);	// Set descent stage fuel tank as default propellant resource[/COLOR]
		break;

	case ASCENT:
[COLOR="red"]		SetDefaultPropellantResource (ph_ascentFuel);	// Set ascent stage fuel tank as default propellant resource[/COLOR]
		break;
	} // End "switch (VesselStatus)"

I think you should be able to see where this is all going. :thumbup:

If we are in launch config we want to make sure that the gear are retracted (and thus not sticking out the sides of the fairing :lol:).

If we are in descent config lets make sure that we're using the correct fuel tank

If we are in ascent config do likewise.

We can add as many instructions to each case as we want, have fun with it.

NOTE: If your switch function gets too crowded or unwieldly you may want to break the instructions for each case off into thier own seperate sub-functions so that your switch looks something like this...

Code:
	switch (VesselStatus)
	{
	case LAUNCH:
		ApplyLaunchConfig ();
		break;
	case DESCENT:
		ApplyDescentConfig ();
		break;
	case ASCENT:
		ApplyAscentConfig ();
		break;
	}

Now obviously those functions will need to be properly declared and defined but at this point this should pose no obstical to you. :thumbup:
 
Last edited:

Hlynkacg

Aspiring rocket scientist
Addon Developer
Tutorial Publisher
Donator
Joined
Dec 27, 2010
Messages
1,870
Reaction score
3
Points
0
Location
San Diego
It occurs to me that if someone is following along step by step the lander isn't really flyable at this point in the tutorial due to the fact that I have not defined any thruster groups.

This is because I have plans for a custom thrust control system that will be implimented after the cockpit (or rather the required displays and switches) have been added.

For the sake of testing, or if you simply decide that you want to forgo advanced thruster modeling all together, add the following to either "clbkPostCreation" or to the bottom of "clbkSetClassCaps"

Code:
	// TEMPORARY THRUST CONTROL CONTROL GROUPS
	THRUSTER_HANDLE	th_group[4];

	// RCS control groups (Rotation)
	th_group[0]	= th_rcsA[4];
	th_group[1] = th_rcsA[6];
	th_group[2] = th_rcsB[0];
	th_group[3] = th_rcsB[2];
	CreateThrusterGroup (th_group, 4, THGROUP_ATT_PITCHUP);

	th_group[0] = th_rcsA[0];
	th_group[1] = th_rcsA[2];
	th_group[2] = th_rcsB[4];
	th_group[3] = th_rcsB[6];
	CreateThrusterGroup (th_group, 4, THGROUP_ATT_PITCHDOWN);

	th_group[0] = th_rcsA[0];
	th_group[1] = th_rcsA[6];
	th_group[2] = th_rcsB[2];
	th_group[3] = th_rcsB[4];
	CreateThrusterGroup (th_group, 4, THGROUP_ATT_BANKLEFT); 

	th_group[0] = th_rcsA[2];
	th_group[1] = th_rcsA[4];
	th_group[2] = th_rcsB[0];
	th_group[3] = th_rcsB[6];
	CreateThrusterGroup (th_group, 4, THGROUP_ATT_BANKRIGHT);

	th_group[0] = th_rcsA[5];
	th_group[1] = th_rcsB[1];
	//th_group[0] = th_rcsA[3];
	//th_group[1] = th_rcsB[7];
	CreateThrusterGroup (th_group, 2, THGROUP_ATT_YAWRIGHT);

	th_group[0] = th_rcsA[7];
	th_group[1] = th_rcsB[3];
	//th_group[0] = th_rcsA[1];
	//th_group[1] = th_rcsB[5];
	CreateThrusterGroup (th_group, 2, THGROUP_ATT_YAWLEFT);

	// RCS control groups (Translation)
	th_group[0] = th_rcsA[2];
	th_group[1] = th_rcsA[6];
	th_group[2] = th_rcsB[0];
	th_group[3] = th_rcsB[4];
	CreateThrusterGroup (th_group, 4, THGROUP_ATT_UP);

	th_group[0] = th_rcsA[0];
	th_group[1] = th_rcsA[4];
	th_group[2] = th_rcsB[2];
	th_group[3] = th_rcsB[6];
	CreateThrusterGroup (th_group, 4, THGROUP_ATT_DOWN);

	th_group[0] = th_rcsB[1];
	th_group[1] = th_rcsB[3];
	CreateThrusterGroup (th_group, 2, THGROUP_ATT_RIGHT);

	th_group[0] = th_rcsA[5];
	th_group[1] = th_rcsA[7];
	CreateThrusterGroup (th_group, 2, THGROUP_ATT_LEFT);

	th_group[0] = th_rcsA[3];
	th_group[1] = th_rcsB[5];
	CreateThrusterGroup (th_group, 2, THGROUP_ATT_FORWARD);

	th_group[0] = th_rcsA[1];
	th_group[1] = th_rcsB[7];
	CreateThrusterGroup (th_group, 2, THGROUP_ATT_BACK);
	// END TEMPORARY THRUSTER GROUPS
 

Hlynkacg

Aspiring rocket scientist
Addon Developer
Tutorial Publisher
Donator
Joined
Dec 27, 2010
Messages
1,870
Reaction score
3
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...
picture.php


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...
picture.php


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...
picture.php


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?
 
Last edited:
Top