Orbiter-Forum  

Go Back   Orbiter-Forum > Orbiter Space Flight Simulator > Tutorials & Challenges
Register Blogs Orbinauts List Social Groups FAQ Projects Mark Forums Read

Tutorials & Challenges Feel free to publish your tutorials, challenges, & flight scenarios in this forum.

Reply
 
Thread Tools
  #1  
Old
Hlynkacg's Avatar
Hlynkacg Hlynkacg is offline
Aspiring rocket scientist


Default Coding a Lunar Lander from the ground up (when you don't know how to code)
by Hlynkacg 12-29-2012, 08:55 PM

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
Spider Lunar Lander *Beta v2.3*
addon.

With that out of the way, Let's Begin...

PART 1: Blank Slate, Now what?

Ok when last I left you we had a .dll based vessel that looked and behaved exactly like a Shuttle PB.



This Shuttle PB is not a Lunar Lander. Let's open our project in visual studio and get to work.

Our first step will be to break our existing code into more managable chunks. To do this start by clicking on the folder marked "Header Files" in your Solution explorer and selecting "Add Item".


*Ignore my project title, it will only give you false hope*

Select "Header" (*.h) file from the menu and give it an appropriate name. This is going to be the primary definition file for our vessel class so I'd name it after the vessel.



We now have a blank header file. As per wikipedia a header file is...

...a file that allows programmers to separate certain elements of a program's source code into reusable files. Header files commonly contain forward declarations of classes, subroutines, variables, and other identifiers. Programmers who wish to declare standardized identifiers in more than one source file can place such identifiers in a single header file, which other code can then include whenever the header contents are required. This is to keep the interface in the header separate from the implementation. The C standard library and C++ standard library traditionally declare their standard functions in header files.

Translated into Cro-Magnon what this allows us to declare any constants or variables once and then use and reuse them across multiple functions, components and even vessels simply by adding a single line to our vessel's source code.

Code:
#include "*your file-name here*.h"
You'll note that our vessel already has one such line that reads.

Code:
#include "orbitersdk.h"
This is the file that tells Visual Studio what to do with instructions like "SetThrusterResource(x, y)" and "SetDockParams(a,b,c)" that are unique to Orbiter.

To populate our header find block of code in our source file that reads...

Code:
*your vessel's name here*::*your vessel's name here* (OBJHANDLE hVessel, int flightmodel)
: VESSEL3 (hVessel, flightmodel)
{
}

*your vessel's name here*::~*your vessel's name here* ()
{
}
These two blocks are your vessel's "Constructor" and "Destructor" functions. They are called any time an instance of your vessel is added to or removed from the simulation. (You may want to label them for future reference)



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



Anyway our vessel's code is now split into two files. Remember to add

Code:
#include "*your file-name here*.h"
to the top of your source file and make sure it compiles.

It' does? Good! Moving on...

PART 2: Visuals

So we've done a bunch of work but our vessel still looks and behaves like a ShuttlePB and not a Lunar Lander or Galor-class Cardassian warship.

Now the sad truth is that most addon request are less interested in Diamond-Hard Spacecraft Simulation then they are in simply finding something that looks the part. So let's make our Shuttle PB look the part.

Go to your vessel's class interface (now in our header file) and add the following lines...

Code:
// ==============================================================
// Shuttle-PB class interface
// ==============================================================

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

	// Orbiter CallBack Functions
	void	clbkSetClassCaps (FILEHANDLE cfg);

private:
	MESHHANDLE	mh_descent, mh_ascent; // Mesh handles

	UINT	mesh_Ascent;		// Ascent stage mesh
	UINT	mesh_Descent;		// Descent stage mesh};
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)
{
	// Load Exterior meshes
	mh_descent		= oapiLoadMeshGlobal("UMMU_Apollo/LEM_DescentStage");
	mh_ascent		= oapiLoadMeshGlobal("UMMU_Apollo/LEM_AscentStage");
}
"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.





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



Much better

Sure, it may fly like a SuttlePB but at least it LOOKS the part.

Tune in Next episode when we'll be covering animations.
Reply With Quote
Views 14535 Comments 48
Total Comments 48

Comments

Old 12-29-2012, 10:45 PM   #2
wehaveaproblem
One step closer
 
wehaveaproblem's Avatar

Default

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
wehaveaproblem is offline   Reply With Quote
Thanked by:
Old 12-30-2012, 12:25 AM   #3
DaveS
Addon Developer
 
DaveS's Avatar


Default

Quote:
Originally Posted by wehaveaproblem View Post
  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!
DaveS is online now   Reply With Quote
Thanked by:
Old 12-31-2012, 11:42 PM   #4
Hlynkacg
Aspiring rocket scientist
 
Hlynkacg's Avatar


Default

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
overloading overloading
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);
	void	clbkPostStep(double simt, double simdt, double mjd);

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"

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

} // End "LM::clbkPostStep"


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:
	enum		doorstate {CLOSED, OPEN, CLOSING, OPENING} HatchStatus;
	double		Hatch_Proc;


	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.


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.

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
	int		clbkConsumeBufferedKey (DWORD key, bool down, char *kstate);	// Process keyboard inputs
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"

// --------------------------------------------------------------
// 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
}
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 going on here"

Compile and test...



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;

	// Animations
	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:"
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); 
	
	anim_Hatch = CreateAnimation(0);
	AddAnimationComponent ( anim_Hatch, 0.0f, 1.0f, &mgt_Hatch);
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.
		}

		SetAnimation( anim_Hatch, hatch_proc);	// Apply process value to animation.
	}
Like the comment says, this will apply our process value to anim_Hatch.

Compile and test...



Success!

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 by Hlynkacg; 12-31-2012 at 07:20 AM.
Hlynkacg is offline   Reply With Quote
Old 01-02-2013, 01:08 AM   #5
Hlynkacg
Aspiring rocket scientist
 
Hlynkacg's Avatar


Default

I had written a full update but then my browser crashed and ate it.



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
	ANIMATIONCOMPONENT_HANDLE	ach_Hatch, ach_Handle;
	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 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
	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);
}
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);
	ach_Hatch	= AddAnimationComponent (anim_Hatch, 0.3f, 1.0f, &mgt_Hatch);
	ach_Handle	= AddAnimationComponent (anim_Hatch, 0.0f, 0.2f, &mgt_HatchHandle, ach_Hatch);
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, ach_Hatch);
...and why assigning (programing)handles to your components is a good idea.

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, 0.3f, 1.0f, &mgt_Hatch);
	ach_Handle	= AddAnimationComponent (anim_Hatch, 0.0f, 0.2f, &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...


Perfect!

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?
Hlynkacg is offline   Reply With Quote
Thanked by:
Old 01-02-2013, 02:15 AM   #6
DaveS
Addon Developer
 
DaveS's Avatar


Default

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

Quote:
Originally Posted by Hlynkacg View Post
 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!
DaveS is online now   Reply With Quote
Old 01-02-2013, 02:57 AM   #7
BruceJohnJennerLawso
Dread Lord of the Idiots
 
BruceJohnJennerLawso's Avatar
Default

Quote:
Originally Posted by DaveS View Post
 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 .
BruceJohnJennerLawso is offline   Reply With Quote
Thanked by:
Old 01-02-2013, 06:20 AM   #8
Hlynkacg
Aspiring rocket scientist
 
Hlynkacg's Avatar


Default

Quote:
Originally Posted by DaveS View Post
 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 .

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 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 by Hlynkacg; 01-02-2013 at 06:26 AM.
Hlynkacg is offline   Reply With Quote
Thanked by:
Old 01-02-2013, 12:09 PM   #9
Ripley
Tutorial translator
 
Ripley's Avatar
Default

Quote:
Originally Posted by Hlynkacg View Post
 I had written a full update but then my browser crashed and ate it....
That's why I >always< write my longer posts with Notepad++ (or anything that can "save" the text).
Ripley is offline   Reply With Quote
Old 01-02-2013, 09:11 PM   #10
Hlynkacg
Aspiring rocket scientist
 
Hlynkacg's Avatar


Default

Quote:
Originally Posted by Ripley View Post
 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...

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

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!

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
	
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
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);

	// 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);
} // 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...

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.

Quote:
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)) 
	{
		// 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.
		}		
		// Load default parameters
		else 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.
		}		
		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);
Feel free to experiment with it.

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 by Hlynkacg; 01-02-2013 at 09:16 PM.
Hlynkacg is offline   Reply With Quote
Thanked by:
Old 01-02-2013, 09:44 PM   #11
DaveS
Addon Developer
 
DaveS's Avatar


Default

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.
DaveS is online now   Reply With Quote
Thanked by:
Old 01-02-2013, 10:01 PM   #12
Hlynkacg
Aspiring rocket scientist
 
Hlynkacg's Avatar


Default

Quote:
Originally Posted by DaveS View Post
 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
Spider Lunar Lander *Beta v2.3*
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.
Hlynkacg is offline   Reply With Quote
Thanked by:
Old 01-02-2013, 10:16 PM   #13
DaveS
Addon Developer
 
DaveS's Avatar


Default

Quote:
Originally Posted by Hlynkacg View Post
 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;
}
DaveS is online now   Reply With Quote
Thanked by:
Old 01-02-2013, 10:34 PM   #14
Hlynkacg
Aspiring rocket scientist
 
Hlynkacg's Avatar


Default

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.
Hlynkacg is offline   Reply With Quote
Thanked by:
Old 01-03-2013, 05:12 PM   #15
Izack
Non sequitur
 
Izack's Avatar

Default

This is an excellent idea for a tutorial! Keep it up.
Izack is offline   Reply With Quote
Thanked by:
Reply

  Orbiter-Forum > Orbiter Space Flight Simulator > Tutorials & Challenges


Thread Tools

Posting Rules
BB code is On
Smilies are On
[IMG] code is On
HTML code is Off
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts
Forum Jump


All times are GMT. The time now is 11:32 PM.

Quick Links Need Help?


About Us | Rules & Guidelines | TOS Policy | Privacy Policy

Orbiter-Forum is hosted at Orbithangar.com
Powered by vBulletin® Version 3.8.6
Copyright ©2000 - 2017, Jelsoft Enterprises Ltd.
Copyright 2007 - 2012, Orbiter-Forum.com. All rights reserved.