19
N0800 Follow This Plane SimConnect Tutorial For Flight Simulator X --------------- How to fly AI Aircrafts Using Waypoints in FSX SDK ---------------

SimConnect Tutorial 0-800

Embed Size (px)

DESCRIPTION

SimConnect Tutorial 0-800

Citation preview

Page 1: SimConnect Tutorial 0-800

N0800

Follow

This Plane

SimConnect

Tutorial

For Flight

Simulator X

---------------

How to fl y

AI Aircrafts

Using

Waypoints

in FSX SDK

---------------

Page 2: SimConnect Tutorial 0-800

N0800 Tutorial

What is this tutorial for? The tutorial is to understand how we send nearby waypoints

to a second aircraft, to fl y it smoothly towards the waypoints. We’ll also drop a parachute

at the location of the waypoints, to visualize where is the second aircraft fl ying to. In the

picture below, our aircraft is on top and the second aircraft is controlled by our SimConnect

program. The console output of the program shows what we are doing.

What do you need to complete this tutorial? MS FSX SDK, which is on the CD

of “MS FSX Professional”. A compiler, which can be MS Visual Studio C++, C# or VB,

which is available for free at Microsoft.

Page 3: SimConnect Tutorial 0-800
Page 4: SimConnect Tutorial 0-800

SimConnect Overview

SimConnect is a gateway that allows external programs to interact with FSX.

SimConnect is part of FSX SDK. So you need to install the SDK if you want to use

SimConnect. It displays itself as a server and the external programs are clients.

SimConnect allows the client to receive information about FSX simulation. The

client may ask details of the current state of the simulation, for instance which objects,

like aircrafts, are currently simulated; or what are the parameters, like the altitude, of

these objects.

An asynchronous request / reply mechanism is used to obtain data. The client

usually sends a request to SimConnect, SimConnect replies to this request later. The client

hasn’t to wait for the reply, it can perform other tasks in the meantime. When SimConnect

is ready to reply, it sends an event message to the client, using a messaging system.

The client needs to receive all event messages, and dispatch them according

to their nature. To do that it monitors continuously for new SimConnect messages thru

an endless loop. When a message is available, the loop transfers the control to a callback

routine designed to handle these messages in a specifi c way.

The callback procedure is the core of the client design. It identifi es the type of

message received, and execute appropriate calls so that the application is aware of replies

to previous requests. Basically a SimConnect client application is a program that sends

requests to SimConnect and manage replies in an asynchronous way in the callback

procedure.

The client may also “subscribe for events notifi cation”. It informs SimConnect

of its interest in being informed about changes, e.g. the simulation being paused, or

an object being removed from the simulation. When such event occurs, SimConnect

just sends an event message to be received and processed by the client in the callback

procedure.

SimConnect allows the client to change the status of the simulation. The client

can move objects, create new ones, control cameras, modify FSX menus, display dialog

boxes, etc. To do that, the client sends data, aimed to some object in the simulation.

SimConnect receives these data asynchronously too. It processes them when possible, and

according to some priority set by the client.

SimConnect manages multiple clients. Clients may reside on the same computer or

not. The connection between the server and the clients relies on IP streams and pipes,

but these details are mostly transparent to the clients programmers. The SimConnect

API manages the communication when data transfers are needed. Clients may ask other

clients to be notifi ed of what they send to SimConnect. The FSX simulation engine by itself

is also a SimConnect client. All messages inbound and outbound are processed by the

server based on priorities that may be assigned by clients. The fi nal order for processing

messages of identical priority is managed by SimConnect.

SimConnect Documentation

The main documentation for SimConnect use is the Help fi le included in FSX SDK.

Unfortunately this documentation is disappointing, it is not at all aimed to start

programming SimConnect applications, it is just the raw documentation of the

SimConnect API, in alphabetical order of the functions. In addition, it is defi netely C++

oriented, and leaves the C# or VB programmer with additional diffi culties related to calling

Program S ta rt

C ontinue = true

S im C onnect_C a llD isp atc h

(M yD is patc hP ro c )

S leep()

C ontinue = = true

void C A LL B A C K

M yD isp atch P ro c(S IM C O N N E C T_ R EC V* pD ata)

sw itch(pD ata->dw ID )

{

case S IM CON N EC T_R EC V_ID_EXC EPTION :

break;

case S IM CON N EC T_R EC V_ID_EVEN T:

break;

case S IM CON N EC T_R EC V_ID_EVEN T_O BJECT_ADD R EM O VE:

break;

case S IM CON N EC T_R EC V_ID_SIM O BJEC T_D ATA:

break;

case …

Program End R outine End

Page 5: SimConnect Tutorial 0-800

the C API from a non C application. Additional mechanisms are needed to check / convert

/ validate data types being sent to SimConnect by the client (MS calls that “marshalling”).

Let’s see with this C portion of code:

struct Struct1

{

char* title;

double latitude;

double longitude;

double altitude;

};

In C# you’ll have to write your code this way:

[StructLayout(LayoutKind.Sequential,

CharSet = CharSet.Ansi, Pack = 1)]

struct Struct1

{

[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]

public String title;

public double latitude;

public double longitude;

public double altitude;

};

In the SDK documentation you’ll fi nd a small set of examples in C# and VB. However all

the API is described using C approach. So be prepared to face additional challenges if you

have or want to use compilers producing “managed code” for the .NET framework.

When it comes to searching the internet for answers not found in the SDK documentation,

you’ll also feel that there is a very limited number of urls reported by your prefered search

engine. Most of them point to MSDN. Many of those pages will discuss something not

working as it should, or will ask for explanations about how things should be done. You

may end thinking this is just emphasizing the fact that there are so many functions in

the API, and so little overview on how to use them. And no SimConnect Primer available

so far. All things considered, you got FSX SDK in FSX professional for a mere 10 bucks

compared to the standard version. That’s OK!

By the way, after MS realized Flight Simulator was a valuable platform for simulation

of any kind, and could be a gold mine in the serious companies sectors, they started

working on the generic simulation engine “Enterprise Simulation Platform”. ESP has now

several versions, but for the time being the documentation related to ESP can be used for

SimConnect in FSX. This is what you’ll fi nd on MSDN.

If you start programming for SimConnect, bear in mind you are tackling new frontiers on

your own. No “beam me up, Scotty!”. So... how do you start programming SimConnect

clients?

IDE and programming language

For the IDE, you’ll probably end up with Visual Studio, which comes for free in

its Express edition (well... you’re pestered to register after a month, so you’ll certainly pay

some price in the end). I don’t know about using Eclipse, which I like defi nitely more than

VSE. I started with VSE to avoid additional diffi culties to code the examples found in the

documentation.

Which language to use for coding SimConnect clients? You may use C/C++ or

MS CLI managed languages (C# , VB, VJ). If you are effi cient with C/C++, you’ll be safer

than with the others. There is also an independent implementation of the SimConnect API

in Java (jSimConnect) with maybe the same drawbacks than MS managed code, but it

could be worth a try.

For Kaspersky customers who want to install Visual Studio. MS has an article

about incompatibility between KAV and VS and suggests installing with KAV inactive. I

had not this problem, however, after I installed VSE, KAV started blocking web pages with

embedded scripts. KAV is now complaining to be unable to load VBScript.dll. Kaspersky

support is leading nowhere. Once this problem existed with leftovers after NAV uninstall,

so the only suggestion they provide is to uninstall NAV properly whatever your problem

is. My KAV licence is expiring in a month, so I’ll live with that until then and uninstall KAV

(properly).

What you need to code a SimConnect Client (with C++)

You need SimConnect.h and SimConnect.lib. The overview of the “SimConnect

SDK Reference” (the SDK help fi le) explains how to to set up your IDE correctly.

For this tutorial, all the code is in the same source fi le. The project is created from the

Win32 Console Application template that comes with VS. The output of the printf calls will

be displayed in the DOS-like window. This is a nice way to debug asynchronous calls.

Page 6: SimConnect Tutorial 0-800

Of course, you need to install the FSX SDK if you didn’t install it with FSX initially.

By creating a SimConnect.ini fi le in your My Documents\Flight Simulator X

Files folder you can also enable a debug window in FSX. This is briefl y explained in the

help fi le. You can copy here the default ini fi le found in the SDK folder. No need to create

one from scratch.

At this time, you now have the blue part of this diagram (1, 2 and 3):

S im C onnect

A P I H eader

V isua l

C ++

C ++

S ource

FS X

S im ula tion

E ngine

S im C onnect

S erver

S im C onnect

A pplica tion (C lient)

S im C onnect

A P I L ibrary

FS X U ser

FS X

S im ula tion

E ngine

S im C onnect

S erver

S im C onnect

A pplica tion (C lient)

S im C onnect

A P I L ibrary

FS X U ser

C om pile T im e

R un T im e

2

3 1

4

5 7

8

9106

The SimConnect API header (3) contains all the declarations needed to work with the

API (6). After you compile your code and link it (4) with the API library, you can run the

executable client (5). This is how it will work:

(5) the client opens a connection with the server (7). When the connection request has

been processed successfully, the server post a message (10) to the client to inform it. The

client can now send requests and data to the server.

To answer such request the server (7) may need to talk with FSX (9).

Conversely FSX (9) will have to inform the server (7) about events occuring in the

simulation. Such events may need to be reported by the server to all or some of its clients

(5).

On his side, the user (8) is controlling FSX. If a client wants to be informed when some

user’s event occurs, it may “subscribe” to it. Such events detected by the simulation engine

(9) will be transferred to the server and then to the client.

If FSX is terminated by the user (or for some other reason), a message will be sent by the

server to the clients.

In the end the client has to close the connection with the server to free any allocated

resources.

We mentionned the server informing (calling back) the client of two events: the

connection process success and FSX being terminated. Technically this is a call of the

client’s callback procedure, with a parameter being a pointer to a message structure. All

messages are declared this way: SIMCONNECT_RECV* pData.

SIMCONNECT_RECV defi nition:

struct SIMCONNECT_RECV

{

DWORD dwSize; // record size

DWORD dwVersion; // interface version

DWORD dwID; // see SIMCONNECT_RECV_ID

};

dwID is an integer identifying the kind of message being received. For a feedback after

a connection request it will be 2, for FSX quitting it will be 3. We won’t manipulate these

values directly. Instead we’ll use the enumerated values provided in SimConnect.h:

// Receive data types

enum SIMCONNECT_RECV_ID {

SIMCONNECT_RECV_ID_NULL,

SIMCONNECT_RECV_ID_EXCEPTION,

SIMCONNECT_RECV_ID_OPEN,

SIMCONNECT_RECV_ID_QUIT,

SIMCONNECT_RECV_ID_EVENT,

SIMCONNECT_RECV_ID_EVENT_OBJECT_ADDREMOVE,

SIMCONNECT_RECV_ID_EVENT_FILENAME,

SIMCONNECT_RECV_ID_EVENT_FRAME,

SIMCONNECT_RECV_ID_SIMOBJECT_DATA,

...

SIMCONNECT_RECV_ID_EVENT_RACE_LAP,

Page 7: SimConnect Tutorial 0-800

};

Depending on the SIMCONNECT_RECV_ID value, we’ll need to cast the intial

SIMCONNECT_RECV* pData to the actual structure it points. For example, if this is an

“OPEN” message identifi ed by pData->dwID == SIMCONNECT_RECV_ID_OPEN, then

pData actually points to a structure declared this way:

struct SIMCONNECT_RECV_OPEN : public SIMCONNECT_RECV

{

char szApplicationName[256];

DWORD dwApplicationVersionMajor;

DWORD dwApplicationVersionMinor;

DWORD dwApplicationBuildMajor;

DWORD dwApplicationBuildMinor;

DWORD dwSimConnectVersionMajor;

DWORD dwSimConnectVersionMinor;

DWORD dwSimConnectBuildMajor;

DWORD dwSimConnectBuildMinor;

DWORD dwReserved1;

DWORD dwReserved2;

};

This structure inherits from SIMCONNECT_RECV.

When we receive this message, we have plenty of data that can be accessed with the

pointer after a cast:

SIMCONNECT_RECV_OPEN* pOpen = (SIMCONNECT_RECV_OPEN*)pData;

pOpen->szApplicationName will be the name of the application. Sure this is not of

a critical interest in this tutorial, but you see the principle.

Regarding the structure returned after we receive a SIMCONNECT_RECV_ID_QUIT

message, it’s exactly the same than the basic one:

struct SIMCONNECT_RECV_QUIT : public SIMCONNECT_RECV

{

};

No fi eld is added.

An answer to a request for the altitude of our aircraft will be more interesting.

So we’ll move on and discuss our real example. Since every client must open a connection

to SimConnect, let’s see how we do that and how we handle the reply from the server.

Message loop

Actually let’s construct the fi rst bricks of our masterpiece ;-) starting with the constructor

of our main class (FollowThisPlane). As you recall, we need to have an endless loop

that wait for incoming message posted by the server, and transfer them to a callback

procedure. We’ll execute this loop just after we have requested to open a connection with

the server:

HANDLE hSimConnect = NULL;

bool keep_going = false;

// Constructor. Establish a connection with SimConnect

void FollowThisPlane()

{

// Connect to FSX

Connect();

if (hSimConnect != NULL)

{

// Connected. Loop until FSX exits

keep_going = true;

while(keep_going)

{

SimConnect_CallDispatch(hSimConnect,

MyDispatchProc, NULL);

Sleep(1);

}

// FSX exited. Close the connection with SimConnect

Disconnect();

}

else

{

// Not able to connect. Not retrying in this version

}

// (the program now quits)

}

The call Connect(); will initiate a connection, but the outcome of this request will

arrive asynchronously later. We’ll have a look at what we do within this call. When

Connect() returns, the variable hSimConnect will have been positionned by

SimConnect when requesting to open a connection. In case we were not able to talk

to SimConnect, this variable will remain NULL. In this case, our constructor will return

Page 8: SimConnect Tutorial 0-800

and the programm will quit. It can happen if FSX is not active when we run our little

SimConnect client.

In case we were able to post our request to open a connection, we’ll set keep_going to

true and enter an infi nite loop. In fact we’ll need to have another part of the program

resetting this fl ag to false at some point, so that we can exit the loop and terminate the

program.

Until then, in this loop we’ll call SimConnect_CallDispatch(hSimConnect,

MyDispatchProc, NULL) and then Sleep(1). SimConnect_CallDispatch

just looks for the next SimConnect message waiting in the connection queue associated

with hSimConnect and instructs SimConnect to call another portion of our code

(MyDispatchProc) to handle this message.

Bear in mind that this function excepted, a SimConnect client is completely asynchronous.

The client waits for a message to arrive, and at the same time performs other tasks, likely

in relation with the messages already received. This means the client must have different

threads working in parallel.The constructor’s thread is likely to be waiting most of the

time. SimConnect will call MyDispatchProc() in a new thread.

When SimConnect_CallDispatch() returns, we just Sleep() for 1 ms to ensure

other processes can run.

Opening a connection with SimConnect server

We mentionned the call to Connect() included in our messages loop. Here is the code

we write in this function:

// Try to connect to FSX

void Connect()

{

HRESULT hr;

if (hSimConnect == NULL)

{

printf(“\nRequesting a SimConnect connection”);

hr = SimConnect_Open(&hSimConnect,

“Follow This Plane”, NULL, 0, 0, 0);

if (hr != S_OK)

{

// FSX may be inactive

printf(“\nError %d”, hr);

}

else

{

// Connection to FSX initialized

printf(

“\nConnection request being processed (%d)”,

hSimConnect);

// (When connection process completes, an

// OPEN message will be received)

}

}

}

Receiving the Open event from SimConnect

Using SimConnect_Open() we requested SimConnect to open a connection with our

client. The server already returned the handle for this connection (in hSimConnect),

however the connection is not yet fi nalized. SimConnect has to complete the request and

then to send an event of type SIMCONNECT_RECV_ID_OPEN. This event will ultimately

be transferred to our callback procedure which needs to take care of it. Let’s see what we

do in this procedure:

// SimConnect sends a message to this client, dispatch it.

void CALLBACK MyDispatchProc(

SIMCONNECT_RECV* pData, DWORD cbData, void *pContext)

{

switch(pData->dwID)

case SIMCONNECT_RECV_ID_OPEN:

// the connection process is complete,

Process_Connected();

break;

}

When the callback procedure is called by SimConnect, pData points to the message.

However, as we have already seen, this message contains only information about the

application and server, useless for this tutorial. Thus we ignore the message data, and

call another function Process_Connected() that must take care of the connection

completion. All messages will be processed using the callback procedure above. We’ll add

more case as soon as we’ll need to process other types of messages. All OPEN messages

Page 9: SimConnect Tutorial 0-800

will be processed the same way: we’ll call Process_Connected().

Processing the connection completion

When entering Process_Connected() called by the callback procedure, we now know

we are connected to SimConnected, so to FSX for all practical purposes. Our scenario for

this client is to have another aircraft controlled by FSX AI following our aircraft. The fi rst

thing is to create this aircraft, but when creating an object, we need to provide a position

(latitude, longitude and altitude). In our case we’ll create it near our own aircraft. So we

need to know were we are. By the way, this program assumes we have already taken off

(because we don’t want to add the code required to takeoff the AI aircraft).

We’ll request our position. “Our” means “user’s”. All objects in the simulation are identifi ed

by a unique number. The user’s aircraft has a constant ID defi ned in SimConnect.h:

SIMCONNECT_OBJECT_ID_USER. There is no particular API function to request a

position, this falls into requesting a data about an object, and happens to be performed

by calling SimConnect_RequestDataOnSimObject(). This function is used with a

pre-defi nition of the data we want. In our case we want 3 data: latitude, longitude and

altitude. What we need to do is to register our set of data into the server. MS calls that a

“data_defi ne”. This defi nition will also tell the server which units we want to use for our

data (an altitude may be returned in feet or in meters for instance)

Our routine to process the connection completion looks like this:

// The connection process is completed

void Process_Connected()

{

printf(“\nConnected.”);

// prepare data definitions

Prepare_Data();

// request the initial position of the user’s aircraft

Request_User_Position(SIMCONNECT_OBJECT_ID_USER);

// (the position will be received later)

}

A call to defi ne our set of data to be returned, another call to send the data request.

Let’s go to the details of the data defi nition. What we see is that we need to maintain a

list of “data-defi ne” identifi ers we’ll use when talking with the server.

We bind the ID with the list of data and units. Then when we want the server to return

the data, we only provide the ID. The server has the defi nition somewhere in its tables

associated with our client.

// Unique IDs of data definitions hosted by SimConnect

static enum DATA {

DATA_ACFT_POSITION,

....

};

We’ll add IDs to this enumeration when we’ll need more. Note that we could just use

plain numbers instead of enum, but this would be a little bit diffi cult to remember them.

Now let’s explain to SimConnect which data we are looking for:

// prepare data definitions

void Prepare_Data()

{

// set up the data definitions

printf(“\nSending Data definitions.”);

SimConnect_AddToDataDefinition(hSimConnect,

DATA_ACFT_POSITION, “Plane Altitude”, “feet”);

SimConnect_AddToDataDefinition(hSimConnect,

DATA_ACFT_POSITION, “Plane Latitude”, “degrees”);

SimConnect_AddToDataDefinition(hSimConnect,

DATA_ACFT_POSITION, “Plane Longitude”, “degrees”);

SimConnect_AddToDataDefinition(hSimConnect,

DATA_ACFT_POSITION, “Heading Indicator”, “degrees”);

SimConnect_AddToDataDefinition(hSimConnect,

DATA_ACFT_POSITION, “Airspeed True”, “knots”);

}

We call 5 times SimConnect_AddToDataDefinition(). Each time we provide

these two fi rst parameters: hSimConnect and DATA_ACFT_POSITION. The fi rst one

is to identify our connection instance to SimConnect. Data defi nition are of course kept

isolated from defi nitions of other connections. The second parameter tells SimConnect

which defi nition we are taking about.

The two last parameters are to be read as a pair “data name”-”unit to use”. You can see

we are also requesting our heading and our speed, we’ll need them too when creating

the AI aircraft, because we will want it to move at some speed (not free falling) and in the

same direction than us. We’ll also provide an attitude, but it will be “straight and level”

whatever our attitude.

Page 10: SimConnect Tutorial 0-800

Now let’s see how to request these data to SimConnect.

// Request the position of the user’s aircraft

void Request_User_Position(SIMCONNECT_OBJECT_ID SimObject)

{

SimConnect_RequestDataOnSimObject(

hSimConnect,

REQUEST_INIT_POSITION,

DATA_ACFT_POSITION,

SimObject,

SIMCONNECT_PERIOD_ONCE);

printf(“\nInitial position requested for %d.”, SimObject);

// (the position will be received later)

}

We provide the handle to our connection hSimConnect, this will be the case for all

functions. SimConnect needs to know who is talking and check we are not unknown

strangers. Let’s forget the second parameter for a couple of minutes. We also provide the

ID of the data we want. With that, SimConnect knows we want latitude, longitude, etc

as well as which units to use for the answer. We then provide the ID of an object in the

simulation. When we called Request_User_Position(), we provided the ID of the

user’s aircraft as a paramater, we are using it here. The last parameter says how many

times we want the data to be returned by SimConnect. As you remember SimConnect is

asynchronous and will not provide the data we want during the call to SimConnect_

RequestDataOnSimObject. Instead it will post a message when ready. In addition

it can post this message at interval. We just need to tell what we want. The different

possibilities are defi ned in the header fi le:

// Object Data Request Period values

enum SIMCONNECT_PERIOD {

SIMCONNECT_PERIOD_NEVER,

SIMCONNECT_PERIOD_ONCE,

SIMCONNECT_PERIOD_VISUAL_FRAME,

SIMCONNECT_PERIOD_SIM_FRAME,

SIMCONNECT_PERIOD_SECOND,

};

The difference between “visual frame” and “sim frame” is about including or not the

frames that are not rendered. However the documentation is not so clear about that.

Anyway SimConnect will send us a nice message with the info we want. We need to

be reminded of our request at that time, because we’ll have so many requests that we

can’t remember for sure... so we’ll provide a number now, and SimConnect will include

in its replies. This mechanism will be needed for every request. So we’ll maintain an

enumeration which is assured to grow up shortly:

// Unique IDs of requests to SimConnect

static enum REQUESTS {

REQUEST_INIT_POSITION,

};

As you can see, this is the second parameter we used in our request.

Now that our request for the user’s aircraft position has been sent, what do we do? Well,

we can’t do anything before receiving the reply. It will come thru a call to our callback

procedure. But we need to handle it. It will have the type SIMCONNECT_RECV_ID_

SIMOBJECT_DATA. So let’s just add another case in our MyDispatchProc() routine:

case SIMCONNECT_RECV_ID_SIMOBJECT_DATA:

// An answer to a data request on an object

Process_Data(pData);

break;

Receiving the aircraft position

Let’s also add this new function:

// A reply to a data request

void Process_Data(SIMCONNECT_RECV *pData) {

SIMCONNECT_RECV_SIMOBJECT_DATA *pObjData =

SIMCONNECT_RECV_SIMOBJECT_DATA *)pData;

DWORD ObjectID = pObjData->dwObjectID;

switch(pObjData->dwRequestID)

{

case REQUEST_INIT_POSITION:

// answer to the initial request for user position

HRESULT hr;

ACFT_PARAM *pS = (ACFT_PARAM*)&pObjData->dwData;

printf(“\nUser %.4f %.4f %.f Hdg=%.f Knts=%.f”,

ObjectID, pS->latitude, pS->longitude,

pS->altitude, pS->heading, pS->airspeed);

// AI position related to the user’s aircraft

{

double AIdistance = .2; // NM from user’s position

double AIRelBearing = 270; // degrees clockwise

double AIHeight = -50; // meters above

Page 11: SimConnect Tutorial 0-800

double AIRelSpeed = 0;// kts above user’s speed

// compute position as Lat / Lon

ACFT_POSITION result = Compute_Location(

pS->latitude, pS->longitude, pS->altitude,

AIdistance,

fmod(pS->heading + AIRelBearing, 360),

AIHeight);

// Prepare data for the creation

SIMCONNECT_DATA_INITPOSITION Init;

Init.Altitude = result.alt;

Init.Latitude = result.lat;

Init.Longitude = result.lon;

Init.Pitch = 0.0;

Init.Bank = 0.0;

Init.Heading = pS->heading;

Init.OnGround = 0;

Init.Airspeed =

(DWORD)(pS->airspeed + AIRelSpeed);

hr = SimConnect_AICreateNonATCAircraft(

hSimConnect,

Container_Title,

Tail_Number,

Init,

REQUEST_CREATE_AI);

if (hr == S_OK)

{

printf(“\nAI aircraft requested”);

AI_Acft_Requested = true;

}

else

{

// problem while creating the AI aircraft

printf(“\AI creation request. Error %d”,

hr);

}

}

break;

}

}

So what do we do here? First we cast our message pointer to the type used with a

SIMCONNECT_RECV_ID_SIMOBJECT_DATA message:

SIMCONNECT_RECV_SIMOBJECT_DATA *pObjData =

SIMCONNECT_RECV_SIMOBJECT_DATA *)pData;

These data are related to an object which ID is in dwObjectID: DWORD ObjectID = pObjData->dwObjectID;

We could check it is actually the same object than the one for which we requested data,

but that’s assumed here. We may limit our check to the request ID:

switch(pObjData->dwRequestID)

{

case REQUEST_INIT_POSITION:

Should we receive data not related to our own request, it would be ignored. What is of

interest here is the values of the requested aircraft parameters. There is a pointer to them

in dwData. This is a pointer to a structure crafted by SimConnect from our data defi nition

(DATA_ACFT_POSITION). We need to defi ne somewhere in our program the same data

structure:

// returned after a data request based on DATA_ACFT_POSITION

struct ACFT_PARAM

{

double altitude;

double latitude;

double longitude;

double heading;

double airspeed;

};

and now we can cast the pointer returned to a pointer to this type:

ACFT_PARAM *pS = (ACFT_PARAM*)&pObjData->dwData;

Creating a new aircraft

At this point in time we have the parameters of the user’s aircraft, but what we want is

to create another aircraft near the user’s aircraft. “Near” will be defi ned by a distance and

a bearing relative to the user (so that we can place the second aircraft “at 1 NM, and

30°” for instance). Since SimConnect will insist on having this point defi ned by its lat

and lon, we’ll need to perform some spherical calculation. We’ll leave this duty to a short

routine not described here. To avoid collisions, we’ll also allow to defi ne the second aircraft

altitude relative to our. And to allow some tuning, we’ll also allow to add some kts to its

speed. This give us these variables that can be adjusted to have some fun.

Page 12: SimConnect Tutorial 0-800

double AIdistance = .2; // NM from user’s position

double AIRelBearing = 270; // degrees clockwise

double AIHeight = -50; // meters above

double AIRelSpeed = 0; // kts above user’s speed

Then, as mentionned we call Compute_Location(), our spherical routine:

// compute position as Lat / Lon

ACFT_POSITION result = Compute_Location(

pS->latitude, pS->longitude, pS->altitude,

AIdistance,

fmod(pS->heading + AIRelBearing, 360),

AIHeight);

ACFT_POSITION is a structure defi ned elsewhere in the program:

struct ACFT_POSITION

{

double lat;

double lon;

double alt;

};

We are now nearly ready to create this awaited new aircraft. We just need to put the data

needed for the creation into the structure requested by the SimConnect API. (this structure

SIMCONNECT_DATA_INITPOSITION is defi ned in the API header).

// Prepare data for the creation of a SimConnect AI object

SIMCONNECT_DATA_INITPOSITION Init;

Init.Altitude = result.alt;

Init.Latitude = result.lat;

Init.Longitude = result.lon;

Init.Pitch = 0.0;

Init.Bank = 0.0;

Init.Heading = pS->heading;

Init.OnGround = 0;

Init.Airspeed = (DWORD)(pS->airspeed + AIRelSpeed);

We ask SimConnect to create the aircraft:

char Container_Title[] = “Mooney Bravo”;

char Tail_Number[] = “N0800”;

HRESULT hr = SimConnect_AICreateNonATCAircraft(

hSimConnect,

Container_Title,

Tail_Number,

Init,

REQUEST_CREATE_AI);

Container_Title is the name of the AI object to create (i.e. the type of object to

create), while Tail_Number is obviousy the call sign. As usual for us now, we need to

provide a request identifi er to SimConnect. Our enumeration for request looks now like

this:

static enum REQUESTS {

REQUEST_INIT_POSITION,

REQUEST_CREATE_AI,

}

How to you fi nd the container title? Another gap in FSX SDK documentation. The list is not

provided, instead you have to look, as explained, into FSX description of objects in your

FSX folders.

If you started coding in C# or any MS managed code, bear in mind that you need to

“marshal” your data structures, with an additional effort for the strings.

The creation function returns a result that is S_OK (defi ned in the API header fi le) or

something else. We just check S_OK to confi rm the request was accepted.

We have submitted our creation request, the next step is to receive the notifi cation of its

completion. It will arrive thru the callback procedure and will be tagged with message

type SIMCONNECT_RECV_ID_EVENT_OBJECT_ADDREMOVE. We need to add some

code in our callback procedure.

Receiving a notifi cation for the creation of an object

Each time an AI object is created into or deleted from the simulation, the clients are

informed by calling their callback procedure with a SIMCONNECT_RECV_ID_EVENT_

OBJECT_ADDREMOVE message (this is the same message both the creation and the

removal). However they need to subscribe to this information. So basically, if you create

an AI object, you are not informed of its creation or removal unless you have specifi cally

asked for.

That may seem strange, but you need to consider the number of objects created by FSX

itself, and by other clients as well. What we are really interested in is a way to be informed

only about the creation of objects we requested ourselves. SimConnect will notify a client,

without a need for prior subscription, each time an object created by the client is assigned

Page 13: SimConnect Tutorial 0-800

an object ID. Note that there is no corresponding notifi cations when these objects are

destructed. This ID assignment notifi cation will help us. However we’ll need also to be

informed of our aircraft removal, because we need to stop sending request to FSX as soon

as our little client has no purpose anymore (in this case maybe we should re-create it? It’s

left up to you...).

The AI aircraft should be removed after a collision (actually not) or some other event FSX

would jugde critical (we lack information on that). It will also be removed if it leaves the

“reality bubble” of the user, that if it is further than something around 100 km. The SDK

documentation talks about that.

It is clear we have to implement the handler for removals. For the same price we’ll have a

handler for additions.

Somewhere in our code, we need to subscribe to object addition and object removal

events. This has to be before the fi rst creation request is submitted. A valid time is when

we send our data defi nition (function Prepare_Data). We can add the code to subscribe

here and rename the function:

void Prepare_Data_And_Events()

{

// set up the data definitions

...

// subscribe to events

printf(“\nSubscribing to events.”);

SimConnect_SubscribeToSystemEvent(hSimConnect,

EVENT_ADDED, “ObjectAdded”);

SimConnect_SubscribeToSystemEvent(hSimConnect,

EVENT_REMOVED, “ObjectRemoved”);

}

SimConnect_SubscribeToSystemEvent is declared this way:

HRESULT SimConnect_SubscribeToSystemEvent(

HANDLE hSimConnect,

SIMCONNECT_CLIENT_EVENT_ID EventID,

const char* SystemEventName

);

It needs two parameters in addition of the connection handle: the SystemEventName

which is a string, and the EventID which is a number. The string tells SimConnect what

we want to be notifi ed for. However when SimConnect sends the notifi cation, it won’t

repeat the string in the message, only EventID. That means EventID is another unique

number that will be defi ned in an enumeration:

// Unique IDs for notified events

static enum EVENTS{

EVENT_ADDED,

EVENT_REMOVED,

};

We need to handle both notifi cations by adding this code in our callback procedure:

case SIMCONNECT_RECV_ID_EVENT_OBJECT_ADDREMOVE:

// AI object added or removed

Process_Object_Added_Removed(pData);

break;

To know if the notifi cation is for an addition or for a removal, we need to check the

uEventID reported in the message and compare it with the event ID provided for the

subscription to this class of events. We know what was the request for. Let’s put that

toghether in the function that we’ll call when notifi ed:

// An object has been added or removed

void Process_Object_Added_Removed(SIMCONNECT_RECV *pData)

{

SIMCONNECT_RECV_EVENT_OBJECT_ADDREMOVE *pAddRem =

(SIMCONNECT_RECV_EVENT_OBJECT_ADDREMOVE *) pData;

switch (pAddRem->uEventID)

{

case EVENT_ADDED:

// AI addition

printf(

“\nNew AI object added to FSX (%d, type %d)”,

pAddRem->dwData, pAddRem->eObjType);

break;

case EVENT_REMOVED:

// AI object removed

printf(

“\nAI object removed (%d)”, pAddRem->dwData);

// let’s check if it is ours

if (pAddRem->dwData == AI_Acft_ID)

{

// Our AI acft has died

keep_going = false;

AI_Acft_Valid = false;

printf(“, our AI aircraft!”);

Page 14: SimConnect Tutorial 0-800

printf(“\nRestart me.”);

}

break;

}

}

In the code above, we cast the message pointer to the actual structure for this event. Then

we look at uEventID to sort additions and removals based on the event ID provided at

the subscription. For the additions we do nothing except leaving a debug trace. For the

removal, we check if the object removed isn’t our aircraft. As we will see in a couple of

minutes, this object ID has been stored in the variable AI_Acft_ID. If the comparaison

confi rms the removal, then we reset the fl ag keep_going that was set when the aircraft

was confi rmed as created. We also reset the fl ag which indicates the AI_Acft_ID

variable contains a valid ID.

You may wonder why we don’t use the EVENT_ADDED case to confi rm our AI aircraft

was created. The reason is that we can’t, because the SIMCONNECT_RECV_EVENT_

OBJECT_ADDREMOVE notifi cation doesn’t carry the Request ID that led to the creation.

Without this information, we cannot know if the new object is the one we are expecting...

So we are bound to handle also the notifi cation of the ID assignment.

Detecting when a new object is assigned its ID by FSX.

In our tutorial, a new category of notifi cations to be handled means a new case in our

callback procedure, and a new function to do the actual work.

case SIMCONNECT_RECV_ID_ASSIGNED_OBJECT_ID:

// an answer to a AI creation request

Process_Object_ID_Assignment(pData);

break;

The new function:

// An object has been created and assigned an ID

void Process_Object_ID_Assignment(SIMCONNECT_RECV *pData)

{

SIMCONNECT_RECV_ASSIGNED_OBJECT_ID *pAssObjData = (SIMCONNECT_

RECV_ASSIGNED_OBJECT_ID *)pData;

// We need to confirm this is one of our objects

switch (pAssObjData->dwRequestID)

{

case REQUEST_CREATE_AI:

// Our AI aircraft

printf(“\nAI Aircraft created (%d)”

pAssObjData->dwObjectID);

AI_Acft_ID = pAssObjData->dwObjectID;

AI_Acft_Valid = true;

// Start a timer

SimConnect_SubscribeToSystemEvent(hSimConnect,

EVENT_TIMER, “4sec”);

printf(“\nSubscribing to Timer ticks.”);

break;

}

}

Again what we do is pretty straightforward. We compare the message Request ID

(dwRequestID) with our request ID used to create the AI aircraft (REQUEST_CREATE_

AI). If they match, we have now a second aircraft up and running. We store its object

ID for later use (removal detection mentioned previously). We also set a fl ag to indicate

AI_Acft_ID is now valid.

AI_Acft_ID = pAssObjData->dwObjectID;

AI_Acft_Valid = true;

Then we subscribe to a timer event sent every 4 seconds. We’ll use it to send a new

waypoint to the AI aircraft. This way we’ll feed the aircraft with waypoints that keep it in

the vicinity.

SimConnect_SubscribeToSystemEvent(hSimConnect,

EVENT_TIMER, “4sec”);

Requesting periodically a new user’s position

So let’s see the second part of this tutorial, where we have to receive ticks, create a

waypoint, and send it to the AI aircraft. Fortunately enough, a non ATC aircraft still knows

how to keep fl ying and how to move towards a waypoint. And the simulation takes care of

this job very smoothly.

The last touch of fun will be to add markers at the location of the waypoints, to have a

visual clue of where the AI aircraft is expected to go.

To receive the timer events, we need to upgrade our callback procedure, and to create

another handler it can call. Those simple events like timer (we’ll also use a pause

Page 15: SimConnect Tutorial 0-800

detection later) are all received under the same message type, SIMCONNECT_RECV_ID_

EVENT.

case SIMCONNECT_RECV_ID_EVENT:

// One of the FSX events this client has subscribed to

Process_Event(pData);

break;

The handler we add now contains this code:

// Message received is a RECV_EVENT

void Process_Event(SIMCONNECT_RECV *pData) {

SIMCONNECT_RECV_EVENT *evt= (SIMCONNECT_RECV_EVENT *)pData;

switch(evt->uEventID)

{

case EVENT_TIMER:

// time to update things...

if (keep_going && !Paused)

{

// request the new position of the user

Request_User_Position(

SIMCONNECT_OBJECT_ID_USER, false);

}

break;

}

}

}

We check the message is for EVENT_TIMER. At this time we should request the current

position of the user aircraft. We would then wait for the server reply, and in the handler

of the reply we would create a new waypoint based on the current position of the user’s

aircraft. This is what we’ll do except that we need to stop creating waypoints when the

simulation is paused or when the AI aircraft has been removed:

if (keep_going && !Paused)

{

// request the new position of the user

Request_User_Position(

SIMCONNECT_OBJECT_ID_USER, false);

}

keep_going will be reset in the handler for object removal notifi cations, while toogling

Paused needs to be linked to a new event from SimConnect. We’ll take care of that soon.

Regarding requesting the current position, it would make sense to use the code we wrote

for asking the initial position. However when we are notifi ed of the user’s position, we

need to know which request is answered to execute the related actions set which are

different. It means we need to have different request IDs, we cannot reuse REQUEST_

INIT_POSITION. We will insert another ID, REQUEST_NEXT_POSITION, in our

enumeration of request IDs. To tell Request_User_Position() which request ID

needs to be used, we use a boolean fl ag, set only for the initial request. This is our handler,

modifi ed to suit both types of requests:

// Request the position of the user’s aircraft

void Request_User_Position(

SIMCONNECT_OBJECT_ID SimObject, bool Initial)

{

SimConnect_RequestDataOnSimObject(

hSimConnect,

Initial ?

REQUEST_INIT_POSITION : REQUEST_NEXT_POSITION,

DATA_ACFT_POSITION,

SimObject,

SIMCONNECT_PERIOD_ONCE);

printf(“\n%s position requested (%d).”,

Initial ? “Initial” : “Current”, SimObject);

// (the position will be received later)

}

We mentionned our need to capture pauses so that we don’t send useless waypoints to

the AI aircraft when the simulation is paused. The waypoints would be all identical, since

the user’s aircraft is not moving, and in addition the AI aircraft would receive them all at

the same time, when the simulation is restarted. Being notifi ed of Pause is a matter of

subscribing to the appropriate event and again to handle the notifi cations. Let’s add a

subscription at the same location than the previous ones:

void Prepare_Data_And_Events()

{

// set up the data definitions

...

// subscribe to events

...

HRESULT hr = SimConnect_SubscribeToSystemEvent(

hSimConnect, EVENT_PAUSE_TOGGLE, “Pause”);

Paused = (hr == 0) ? false : true;

Page 16: SimConnect Tutorial 0-800

}

We are subscribing to SimConnect event “Pause”, and we use the event ID EVENT_

PAUSE_TOGGLE to remember what is this event. (for later, when we’ll be notifi ed and

provided this event ID) This new ID has been added to our EVENTS enumeration (this

is straightforward, not need to show you the updated code). Note that the order of the

symbols in the enum defi nition has no importance, only their uniqueness is meaningful.

When we subscribe to events like Pause, the call to SimConnect returns the current status

of the parameter. Here it returns 0 if the simulation is currently unpaused. this allow us to

set our Paused fl ag to the appropriate value immediately.

The notifi cations are handled this way, reusing the existing event handler already used to

handle EVENT_TIMER:

switch(evt->uEventID)

{

case EVENT_TIMER:

...

case EVENT_PAUSE_TOGGLE:

// the simulation is paused or unpaused

Paused = evt->dwData == 0 ? false : true;

break;

}

We get the new status of the simulation in dwData. We update our Paused fl ag

accordingly.

Computing the position of the next waypoint

We need to create a waypoint when we are notifi ed of the current user’s position. The

corresponding data request is submitted every 4 sec by the EVENT_TIMER handler. We

already have a handler that processes notifi cations of data delivery (Process_Data()),

we just need to adapt it so that it can handle replies to REQUEST_NEXT_POSITION

requests in addition to replies to REQUEST_INIT_POSITION requests.

case REQUEST_INIT_POSITION:

...

...

break;

case REQUEST_NEXT_POSITION:

// Periodic update of the user’s position

// We send the coordinates of a waypoint nearby

// But only if the AI aircraft is flying...

if (AI_Acft_Valid && keep_going)

{

printf(“\nNew position received.”);

{

// WP position and desired speed at the WP

double WPdistance = .5; // NM

double WPRelBearing = 0; // degrees clockwise

double WPHeight = -50; // meters above

double WPRelSpeed = 5; // kts above

// Compute Lat / Lon of the WP

ACFT_POSITION result = Compute_Location(

pS->latitude, pS->longitude,

pS->altitude, WPdistance,

fmod(pS->heading + WPRelBearing, 360),

WPHeight);

// force VS calculation

unsigned long flags =

SIMCONNECT_WAYPOINT_SPEED_REQUESTED |

SIMCONNECT_WAYPOINT_COMPUTE_VERTICAL_SPEED;

// (code to create the WP goes here)

// (see next section)

When we receive the user’s aircraft position from SimConnect, we check we really need to

create a waypoint:

if (AI_Acft_Valid && keep_going)

i.e. the AI aircraft creation has taken place (confi rmed by AI_Acft_Valid) and the AI

aircraft hasn’t been destructed (confi rmed by keep_going). The position of the waypoint

will be relative to the position of the user’s aircraft: distance in NM, relative angle, altitude

difference, speed difference.

// WP position and desired speed at the WP

double WPdistance = .5; // NM

double WPRelBearing = 0; // degrees clockwise

double WPHeight = -50; // meters above

double WPRelSpeed = 5; // kts above

Page 17: SimConnect Tutorial 0-800

Since we need to provide a latitude and longitude rather than a distance and a bearing

for the creation of the waypoint, we call again our spherical routine. We ensure the

absoulte bearing is in the range 0-360° by calling a fmod.

When creating the waypoint, we’ll specify a speed. This is the desired speed for the aircraft

when it reaches the waypoint. Similarly we want FSX simulation to compute whatever

vertical speed is required to reach the altitude at the waypoint. When calling the function

that creates the WP, we’ll need to provide fl ags that ask for the speed and the vertical

speed to be used. They are ORed:

flags =

SIMCONNECT_WAYPOINT_SPEED_REQUESTED |

SIMCONNECT_WAYPOINT_COMPUTE_VERTICAL_SPEED;

We have collected all the info we need to create the WP.

Creating a waypoint

First, we goup the WP data in an array of SIMCONNECT_DATA_WAYPOINT elements. If

we want to send multiple waypoints, then we describe one WP per array element.

// Prepare data for the creation of a WP

SIMCONNECT_DATA_WAYPOINT waypoint[1];

waypoint[0].Latitude = result.lat;

waypoint[0].Longitude = result.lon;

waypoint[0].Altitude = result.alt;

waypoint[0].Flags = flags;

waypoint[0].ktsSpeed = pS->airspeed + WPRelSpeed;

waypoint[0].percentThrottle = 0;

The percentThrottle value is the quantity of power we want for the aircraft when

it reaches the WP. We don’t use it here (we should force its use by adding a fl ag to our

flags value).

To send the WP to the aircraft, we’ll just use the function that “sends data” to an object.

Sending data to an object requires a data defi nition to tell SimConnect how to understand

what we are sending. We already defi ned data, we’ll just add the new defi nition to the

existing ones.

As the data defi nition needs to be assigned a unique ID, we’ll also update our data

defi nition enumeration to add the symbol DATA_NEXT_POSITION:

// Unique IDs of data definitions hosted by SimConnect

static enum DATA {

DATA_ACFT_POSITION,

DATA_NEXT_POSITION,

};

Let’s now defi ne these WP data:

void Prepare_Data_And_Events()

{

// set up the data definitions

...

...

SimConnect_AddToDataDefinition(

hSimConnect, DATA_NEXT_POSITION,

“AI Waypoint List”, “number”,

SIMCONNECT_DATATYPE_WAYPOINT);

...

...

}

To defi ne DATA_NEXT_POSITION, we send only one piece of data “AI Waypoint

List”, the unit has to be tagged “number” and the structure of the data is

SIMCONNECT_DATATYPE_WAYPOINT.

Let’s send our WP as a “data” using SimConnect_SetDataOnSimObject(). The

object we target is our AI aircraft, which Id is AI_Acft_ID. We need to tell SimConnect

how many elements there is in the array (ARRAYSIZE(waypoint)), and what is the

total size of the data. We also need to provide a pointer to the data (waypoint) as well

as the the data defi nition (DATA_NEXT_POSITION). // Send the WP to the AI aircraft

hr = SimConnect_SetDataOnSimObject(

hSimConnect, DATA_NEXT_POSITION,

AI_Acft_ID, 0, ARRAYSIZE(waypoint),

sizeof(waypoint[0]), waypoint);

printf(“\nNew waypoint sent to AI aircraft);

That’s it. The AI aircraft is now fl ying towards this WP. It would be nice to visualize it. This

will be the icing on the cake.

Creating a marker

Page 18: SimConnect Tutorial 0-800

The green vertical arrow or the point of interest objects used in missions would be

perfect markers to visualize the location of a waypoint. However, I don’t know how to

use them in a SimConnect application. It seems the only objects we can create are those

found in the SimObjects folder of FSX. So the best I can think of is a parachute.

Like we did for the AI aircraft, we need to tell SimConnect where to create the object. This

will be described in a structure SIMCONNECT_DATA_INITPOSITION.

// Prepare data for a SimConnect AI object

SIMCONNECT_DATA_INITPOSITION Init;

Init.Altitude = result.alt;

Init.Latitude = result.lat;

Init.Longitude = result.lon;

Init.Pitch = 0.0;

Init.Bank = 0.0;

Init.Heading = pS->heading;

Init.OnGround = 0;

Init.Airspeed = 0;

With this set of data, we can create the little parachute. We’ll use the SimConnect function

SimConnect_AICreateSimulatedObject which allow us to create objects that

don’t fl y. // Create an AI object to mark the position

SimConnect_AICreateSimulatedObject(hSimConnect,

“Food_pallet”,

Init,

REQUEST_ADD_MARKER);

Note that we used the new request ID REQUEST_ADD_MARKER which will allow us

to detect the creation completion. As usual, the enumeration of request IDs has been

updated.

The name of the container to create is found in the confi guration fi le of the object in FSX

SimObjects folder. There is no list of objects in the SDK help fi le, only some examples.

If you run this program now, it’ll work. Ensure you launch it while you’re already fl ying

your aircraft in FSX. If this is not the case, the AI aircraft will be created with a speed

of 0 kt. And maybe below the ground level, depending on the settings you chose for the

position where to create the AI....

You’ll see the AI aircraft poping up nearby out of nowhere. And also those magenta

parachutes dropped every four seconds.They will materialize your route on the ground

(actually a route parallel to your route). When we send a single WP to the aircraft, the

previous WPs are cancelled from its fl ight plan. Only the most recent parachute is actually

materializing an active WP. If you want, you can add some code to your program, to

remove the previous parachute when a new one is created. It should be in the section

of code that is called when we are notifi ed that an ID has been assigned to our last

parachute (Process_Object_ID_Assignment()):

case REQUEST_ADD_MARKER:

// no more than one marker in the simulation

Previous_Marker = Last_Marker;

Last_Marker = pAssObjData->dwObjectID;

if (Previous_Marker != NULL)

{

// remove the previous marker

SimConnect_AIRemoveObject(hSimConnect,

Previous_Marker, REQUEST_REMOVE_MARKER);

}

Also add these two declarations somewhere:

DWORD Previous_Marker;

DWORD Last_Marker;

With that last set of instructions, we have completed the tutorial. The full code

contains non signifi cant additions that you will be able to discover by reading thru

FollowThisPlane.cpp source.

PS: the code used to compute the coordinates of a point based on its distance and

bearing relative to a point with known coordinates is:

const double PI = 3.14159265358979323846;

const double DEG_TO_RAD = 3.14159265358979323846 / 180.0;

const double EARTH_RADIUS_NM = 6371.008 / 1.852;

double lat1 = refLat * DEG_TO_RAD;

double lon1 = refLon * DEG_TO_RAD;

double d_R = dist / EARTH_RADIUS_NM;

double brng = bearing * DEG_TO_RAD;

result.lat = (

asin(sin(lat1)*cos(d_R) +

cos(lat1)*sin(d_R)*cos(brng)))

Page 19: SimConnect Tutorial 0-800

/ DEG_TO_RAD;

result.lon = (

lon1 +

atan2(

sin(brng)*sin(d_R)*cos(lat1),

cos(d_R)-sin(lat1)*sin(result.lat)))

/ DEG_TO_RAD;

The result is not good, though for the purpose it’s suffi cient. You’ll see the effect when

setting the waypoint position at some distance (1/2 NM) and the bearing at 360°. The WP

is not created directly ahead, but on a side. Regardless of the bearing set, when your turn

your aircraft, the position of the parachute creation will move related to the heading, it

will change from left to right or right to left.

I’ve no clue about what’s wrong, if you see where is the problem... send me your tested

code. Thanks.

( c ) 2009, RWY AheadYou can do whatever you want with this tutorial and its code, it’s public domain.

This code is for simulation only, do not use it in nuclear plants without permission.