SUMMARY: Full simulatenous dual control support for host and guest. Flight model runs on both PC's with periodic updates to ensure everyone is "on the same page". Each person has a controloverride, if the otehr party will not let go when you holler "my airplane". The "master" flight model can be passed back and forth from host to guest(s).
And one of my favorites... push installing the plane. This allows, for example, you to show me your new plane you just finished and take me for a ride and let me take the stick for a few minutes and maybe try a landing after your first touch-and-go-demonstration. Obviously that won't be feasible on all simulators, platforms, and planes... but it's nice functionality. I wouldn't have to download, unzip, read, browse, etc... whatever the procedure is for the sim/platform in question.
I'm BRAND new to FG again since investigating briefly back in 2002 for a little bit (in the last year I discovered X-Plane, its Mars feature, and Plane-Maker and am very interested in the same support for FG, I'll browse and search the forums and get caught up before I pester too much with questions.
I'lll attach the work-in-progress code I have so far, it's not quite complete, but illustrates the ideas. I imagine some or maybe even MUCH of this functionality is already built into FG or has been worked on by someone. And if this is off topic here, please steer me in the right direction?
Thanks,
---Greg / NegativeWashout
negativewashout@gmail.com
PS. YOUTUBE button? Way cool. Rather than misuse it as a first test here, I'll just share the URL!
http://www.youtube.com/user/negativewashout
*
- Code: Select all
/*****************************************************************************
Dual controls plugin
6-9-10
negativewashout@gmail.com
written with an eye for being cross-simulator and cross-platform,
including X-Plane, FlightGear, Flight Simulator X where possible.
C++-like pseudocode
*****************************************************************************/
class PilotControlPostion
{
public:
float getElevatorCommand();
float getAileronCommand();
float getElevatorCommand();
float getThrottleCommand();
float getSpoilerCommand();
bool getMyAirplaneButton();
bytearray getMetadata();// optional byte array/string data
private:
float timestamp; //exactly when the reading was taken
float elevator; //range -1.0 (down) to +1.0 (up)
float aileron; //range -1.0 (left) to +1.0 (right)
float rudder; //range -1.0 (left) to +1.0 (right)
float throttle; //range 0.0 (idle/cut) to +1.0 (full)
float spoiler; //range 0.0 (clean) to +1.0 (full)
bool myairplanebutton; // 1 = pressed
bytearray metadata; first 2 bytes hold length else two zeros;
}
class PlaneData
{
/*****************************************************************************
Mostly for future expansion. Used to hold data and geometry of the plane, textures and panel from
a requested push-installation by the client to the host. Clearly some planes this would be easy
on, some complicated and time consuming and/or impossible.
For the complex ones, simplified geometry and low-res or no/default textures are possible solutions.
If the flight model is a problem running on the guest with the simplified plane, a user-enabled
override could allow the flight model to run on the host only – even when the guest is the Pilot
Flying (PF).
****************************************************************************/
public:
bool installPlane();
bool uninstallPlane();
bool verifyInstallation();
private:
string description;
bytearray acf_file; //first 4 bytes hold length else zeros;
bytearray paint1; //first 4 bytes hold length else zeros;
bytearray paint2; //first 4 bytes hold length else zeros;
bytearay metadata; //first 4 bytes hold length else zeros;
}
class FlightModel
{
public:
//extensive methods and functions here, only a very few used for illustrative purposes
bool setLocalFlightModel(FlightModel); sets member variables from another obj
bool saveLocalFlightModel(); // sets member variables locally from current sim's flight model
bool loadLocalFlightModel(); // sets the current running flight model locally from variables
private:
//extensive members clearly needed here, only a very few used for illustrative purposes
double longitude;
double latitude;
double altitude;
int planet; // 0=Earth, 1=Mars, 2=Earth's Moon, 3=Sun, 4=the Death Star, etc.
float gravity; Earth = 1.0, Mars is about 0.38, the moon ~0.16. The Death Star LOOKED like 1G...
float pitch;
float bank;
float yaw;
float angular_velocity_pitch;
float angular_velocity_roll;
float angular_velocity_yaw;
float true_airspeed;
float windvelocity;
float winddirection;
double outsideairtemperature;
double outsideairpressure;
}
class Network
{
/**** oversimplified for now, leaving this open ****/
public:
bool TransmitCommand(bytearray); //returns 0 if OK, 1 if error
bytearray getCommandString(); //returns command string or err msg
private:
bytearray commandstring; // byte 0-1=length; byte 2=command token, bytes 3+ command string
}
class CommandParser
{
public:
int getCommand(bytearray commandstring); // returns command token
int getCommandLength(bytearray commandstring); // returns length of command string array
bytearray getCommand(bytearray commandstring); // returns the command string (bytes 3-end)
private:
bytearray commandstring; //byte 0-1=length; byte 2=command token, bytes 3+ command string
int commandlength; //length of command string (not including 3 byte overhead)
int command; //command token
}
bool ControlInputSmoother(&PilotControlPostion)
{
/*
Reserved for future expansion; algorithms to deal with inaccuracies and network
lag/delays. Simple predictable algorithms could be tested alongside creative AI ideas. Each
PilotControlPosition object has a timestamp embedded in it for use in analysis/interpolation.
*/
}
PilotControlPostion getLocalControlPos()
{
//reads local controls
}
PilotControlPostion CombineControlPositons(PilotControlPostion, PilotControlPostion)
{
/* combines two controls, taking into account remoteControlInputEffectiveness,
localControlInputEffectiveness, myairplanebutton statuses, localControlsEngaged,
remoteControlsEngaged
*/
}
bytearray getLocalCommand()
{
//returns local command string for flaps, gear, non-flying commands, etc.
}
FlightModel processFlightModelBFrame(bytearray remotecommand)
{
/*******************************************************************************************
Embedded in the remotecommand string will be FlightModel delta information; just the
changes since the last flight model transmission, plus a keyframe number. In the event of
packet loss, there would be a missing B-Frame, making it impossible to extrapolate the current
FlightModel.
If a B-Frame comes in and missing B-Frames are detected, transmit the last good frame# and
ignore B-frames built subsequently until a new transmission is received built on the last good
B-frame. The local simulation will continue to run its current flight model until a valid
update can be received.
If B-Frame loss problematic, Flightmodel delta information could be modified to reference
multiple previous frames, requiring a retransmission only if both/all referenced frames are
unreferenceable. I'd say we implement both options and experiement with performance in various
configurations.
Flight models will always continue to run and update on each local simulator on their own;
the FlightModel object update is to ensure all machines are running the same flight model.
*******************************************************************************************/
}
main()
{
//many initialization steps omitted here for simplification
//command tokens
const int NOOP = 0;
const int CONTROLPOSUPDATE = 1;
const int SETFLAPS = 2;
const int SETGEAR = 3;
const int ELEVATORTRMUP = 4;
const int ELEVATORTRIMDOWN = 5;
const int AILERONTRIMLEFT = 6;
const int AILERONTRIMRIGHT = 7;
const int AILERONTRIMLEFT = 8;
const int AILERONTRIMRIGHT = 9;
const int MOUSEACTION = 10; //incl left, mid, right click, double-click, X,Y
//possible method of enabling access to other aircraft controls
const int VIEWCOMMAND = 11; //if enabled, allows the remote pilot to set local view
const int PAUSE = 12;
const int CLOCKSYNC = 13; // needed for control input timestamps and flight model timing
const int LOCALCONTROLSENGAGE = 14; //additional override support
const int LOCALCONTROLSDISENGAGE = 15; //additional override support
const int FLIGHTMODELUPDATE = 16; //key frame or flightmodel delta
//misc constants
const float CONTROLPOSMAX = 1.0 (maximum allowable absolute position)
const int NUMBEROFCONTROLS = 2 (unused currently; could be set higher for future expansion)
//variable initialization
float localControlInputEffectiveness = 1.00; //our controls are initially 100% effective
float remoteControlInputEffectiveness = 0.85; //85% effective for illustrative & testing purposes
int flightmodelowner = 0; //0=local or seat # to support multiple inputs
float controlposfrequency = 10.0; //initially ten times per second
float flightmodelfrequency = 5.0; initially five times per second
int flightmodelkeyframemaxinterval = 50; // max interval between flight model key frames
int flightmodelBframecount = 0; // iterator from zero through flightmodelkeyframemaxinterval
bool localControlsEngaged = 1;
bool remoteControlsEngaged = 1;
//object initialization
Network networkobj = new Network(); //oversimplified for now
PilotControlPosition localControlInput = new PilotControlPosition();
PilotControlPosition remoteControlInput = new PilotControlPosition();
PilotControlPosition combinedControlInput = new PilotControlPosition();
CommandParser commandparserobj = new CommandParser();
FlightModel LocalFlightModel = new FlightModel();
FlightModel remoteFlightModel = new FlightModel();
FlightModel recentFlightModelBFrameCommands[flightmodelkeyframemaxinterval] =
new FlightModel(); //array
bool exit=0; //flag to exit main loop
//main loop
while(exit==0)
{
//Get remote command and process
bytearray remotecommand = networkobj.getCommand();
int remotecommandtoken = commandparserobj.getCommand(remotecommand);
if(remotecommandtoken == CONTROLPOSUPDATE)
{
bytearray remotecommandstring =
commandparserobj.GetCommandString(remotecommand);
UpdateControlPosition(remotecommandstring)
}
if(remotecommandtoken == SETFLAPS)
{
//set flaps accordingly
}
if(remotecommandtoken == SETGEAR)
{
//set gear accordingly
}
if(remotecommandtoken == FLIGHTMODELUPDATE)
{
if(flightmodelowner != 0)
{
//process update, be it a FlightModel object key frame or B-frame
}
}
if((1.0/controlposfrequency) seconds has passed, take another local controls reading)
{
localControlInput = getLocalControlPos()
if(changed since last reading)
{
bool err = 0; // initialization
combinedControlInput = CombineControlPositons(
localControlInput, remoteControlInput,
localControlInputEffectiveness, remoteControlInputEffectiveness);
err = ControlInputSmoother(&combinedControlInput);
bytearray controlposupdatescommand; //initialize
//(prepare controlposupdatecommand);
err = TransmitCommand(controlposupdatescommand);
}
}
if((1.0/ flightmodelfrequency seconds has passed, transmit another local flight model)
{
if(flightmodelowner==0) // if we own it
{
flightmodelBframecount++;
if(flightmodelBframecount > flightmodelkeyframemaxinterval)
{
//transmit a full flight model
flightmodelBframecount = 0;
}
else
{
//transmit flight model delta
}
}
}
//Get local command and process/transmit
bytearray localcommand = getLocalCommand();
int localcommandtoken = commandparserobj.getCommand(remotecommand);
if(localcommandtoken)
{
bool err = TransmitCommand(localcommand)
}
}
}