Problem Unable to compile Atlantis sample (VC++2008)

N_Molson

Addon Developer
Addon Developer
Donator
Joined
Mar 5, 2010
Messages
9,296
Reaction score
3,270
Points
203
Location
Toulouse
Hello,

I'm trying to compile the Atlantis sample that comes with Orbiter 2010 P1 but I have the following errors :

It seems that lights are causing the problem, and also I get a lot of "undeclared identifiers". I don't get it, because I managed to get the lights working in another addon and everything in the Atlantis.h file seems in order... :shrug:

So if someone has an idea, you're welcome... :tiphat:

Error messages :

Code:
------ Début de la génération : Projet : Atlantis, Configuration : Debug Win32 ------
Compilation en cours...
Common.cpp
g:\Orbiter 2010 P1 - Main\Orbitersdk\samples\Discovery\Atlantis\Atlantis.h(348) : error C2143: erreur de syntaxe : absence de ';' avant '*'
g:\Orbiter 2010 P1 - Main\Orbitersdk\samples\Discovery\Atlantis\Atlantis.h(348) : error C4430: spécificateur de type manquant - int est pris en compte par défaut. Remarque : C++ ne prend pas en charge int par défaut
g:\Orbiter 2010 P1 - Main\Orbitersdk\samples\Discovery\Atlantis\Atlantis.h(348) : error C4430: spécificateur de type manquant - int est pris en compte par défaut. Remarque : C++ ne prend pas en charge int par défaut
Atlantis.cpp
g:\orbiter 2010 p1 - main\orbitersdk\samples\discovery\atlantis\Atlantis.h(348) : error C2143: erreur de syntaxe : absence de ';' avant '*'
g:\orbiter 2010 p1 - main\orbitersdk\samples\discovery\atlantis\Atlantis.h(348) : error C4430: spécificateur de type manquant - int est pris en compte par défaut. Remarque : C++ ne prend pas en charge int par défaut
g:\orbiter 2010 p1 - main\orbitersdk\samples\discovery\atlantis\Atlantis.h(348) : error C4430: spécificateur de type manquant - int est pris en compte par défaut. Remarque : C++ ne prend pas en charge int par défaut
.\Atlantis\Atlantis.cpp(124) : error C2065: 'engine_light' : identificateur non déclaré
.\Atlantis\Atlantis.cpp(124) : error C3861: 'AddPointLight' : identificateur introuvable
.\Atlantis\Atlantis.cpp(125) : error C2065: 'engine_light' : identificateur non déclaré
.\Atlantis\Atlantis.cpp(125) : error C2227: la partie gauche de '->SetIntensityRef' doit pointer vers un type class/struct/union/générique
        le type est ''unknown-type''
.\Atlantis\Atlantis.cpp(608) : error C2065: 'GRP_cargodooroutR' : identificateur non déclaré
.\Atlantis\Atlantis.cpp(608) : error C2065: 'GRP_cargodoorinR' : identificateur non déclaré
.\Atlantis\Atlantis.cpp(608) : error C2065: 'GRP_radiatorFR' : identificateur non déclaré
.\Atlantis\Atlantis.cpp(608) : error C2065: 'GRP_radiatorBR' : identificateur non déclaré
.\Atlantis\Atlantis.cpp(611) : error C2065: 'GRP_cargodooroutL' : identificateur non déclaré
.\Atlantis\Atlantis.cpp(611) : error C2065: 'GRP_cargodoorinL' : identificateur non déclaré
.\Atlantis\Atlantis.cpp(611) : error C2065: 'GRP_radiatorFL' : identificateur non déclaré
.\Atlantis\Atlantis.cpp(611) : error C2065: 'GRP_radiatorBL' : identificateur non déclaré
.\Atlantis\Atlantis.cpp(619) : error C2065: 'GRP_radiatorFR' : identificateur non déclaré
.\Atlantis\Atlantis.cpp(622) : error C2065: 'GRP_radiatorFL' : identificateur non déclaré
.\Atlantis\Atlantis.cpp(632) : error C2065: 'GRP_nosedoorL' : identificateur non déclaré
.\Atlantis\Atlantis.cpp(635) : error C2065: 'GRP_nosedoorR' : identificateur non déclaré
.\Atlantis\Atlantis.cpp(638) : error C2065: 'GRP_nosewheel' : identificateur non déclaré
.\Atlantis\Atlantis.cpp(638) : error C2065: 'GRP_nosegear' : identificateur non déclaré
.\Atlantis\Atlantis.cpp(641) : error C2065: 'GRP_geardoorR' : identificateur non déclaré
.\Atlantis\Atlantis.cpp(644) : error C2065: 'GRP_geardoorL' : identificateur non déclaré
.\Atlantis\Atlantis.cpp(647) : error C2065: 'GRP_wheelR' : identificateur non déclaré
.\Atlantis\Atlantis.cpp(647) : error C2065: 'GRP_gearR' : identificateur non déclaré
.\Atlantis\Atlantis.cpp(647) : error C2065: 'GRP_wheelL' : identificateur non déclaré
.\Atlantis\Atlantis.cpp(647) : error C2065: 'GRP_gearL' : identificateur non déclaré
.\Atlantis\Atlantis.cpp(661) : error C2065: 'GRP_startrackers' : identificateur non déclaré
.\Atlantis\Atlantis.cpp(661) : error C2065: 'GRP_KUband1' : identificateur non déclaré
.\Atlantis\Atlantis.cpp(661) : error C2065: 'GRP_KUband2' : identificateur non déclaré
.\Atlantis\Atlantis.cpp(664) : error C2065: 'GRP_KUband2' : identificateur non déclaré
.\Atlantis\Atlantis.cpp(667) : error C2065: 'GRP_KUband1' : identificateur non déclaré
.\Atlantis\Atlantis.cpp(667) : error C2065: 'GRP_KUband2' : identificateur non déclaré
.\Atlantis\Atlantis.cpp(678) : error C2065: 'GRP_flapR' : identificateur non déclaré
.\Atlantis\Atlantis.cpp(678) : error C2065: 'GRP_flapL' : identificateur non déclaré
.\Atlantis\Atlantis.cpp(678) : error C2065: 'GRP_aileronL' : identificateur non déclaré
.\Atlantis\Atlantis.cpp(678) : error C2065: 'GRP_aileronR' : identificateur non déclaré
.\Atlantis\Atlantis.cpp(686) : error C2065: 'GRP_flapL' : identificateur non déclaré
.\Atlantis\Atlantis.cpp(686) : error C2065: 'GRP_aileronL' : identificateur non déclaré
.\Atlantis\Atlantis.cpp(689) : error C2065: 'GRP_flapR' : identificateur non déclaré
.\Atlantis\Atlantis.cpp(689) : error C2065: 'GRP_aileronR' : identificateur non déclaré
.\Atlantis\Atlantis.cpp(699) : error C2065: 'GRP_rudderR' : identificateur non déclaré
.\Atlantis\Atlantis.cpp(699) : error C2065: 'GRP_rudderL' : identificateur non déclaré
.\Atlantis\Atlantis.cpp(707) : error C2065: 'GRP_rudderR' : identificateur non déclaré
.\Atlantis\Atlantis.cpp(710) : error C2065: 'GRP_rudderL' : identificateur non déclaré
.\Atlantis\Atlantis.cpp(724) : error C2065: 'GRP_Shoulder' : identificateur non déclaré
.\Atlantis\Atlantis.cpp(730) : error C2065: 'GRP_Humerus' : identificateur non déclaré
.\Atlantis\Atlantis.cpp(736) : error C2065: 'GRP_radii' : identificateur non déclaré
.\Atlantis\Atlantis.cpp(736) : error C2065: 'GRP_RMScamera' : identificateur non déclaré
.\Atlantis\Atlantis.cpp(736) : error C2065: 'GRP_RMScamera_pivot' : identificateur non déclaré
.\Atlantis\Atlantis.cpp(742) : error C2065: 'GRP_wrist' : identificateur non déclaré
.\Atlantis\Atlantis.cpp(748) : error C2065: 'GRP_endeffecter' : identificateur non déclaré
Génération de code en cours...
Le journal de génération a été enregistré à l'emplacement "file://g:\Orbiter 2010 P1 - Main\Orbitersdk\samples\Discovery\Atlantis\Debug\BuildLog.htm"
Atlantis - 55 erreur(s), 0 avertissement(s)
========== Génération : 0 a réussi, 1 a échoué, 0 mis à jour, 0 a été ignoré ==========

Atlantis.h

Code:
// ==============================================================
//                 ORBITER MODULE: Atlantis
//                  Part of the ORBITER SDK
//          Copyright (C) 2001-2003 Martin Schweiger
//                   All rights reserved
//
// Atlantis.h
// Class interface of Atlantis (Space Shuttle) vessel class
// module and associated subclasses (SRB, tank)
// ==============================================================

#ifndef __ATLANTIS_H
#define __ATLANTIS_H

#include "orbitersdk.h"
#include <math.h>

// ==========================================================
// Some Orbiter-related parameters
// ==========================================================

const double ORBITER_EMPTY_MASS = 104326.0;
// Orbiter empty mass [kg]

const double ORBITER_MAX_PROPELLANT_MASS = 21600.0;
// Amount of fuel the orbiter can hold in internal OMS tanks

const double ORBITER_MAIN_THRUST = 1668652.0 * 1.25;
// Vacuum thrust rating per main engine [N] (x3 for total)
// assuming vacuum thrust is 5/4 liftoff thrust

const double ORBITER_OMS_THRUST = 26700.0;
// Vacuum thrust per unit for Orbital Maneuvering System [N] (x2 for total)

const double ORBITER_RCS_THRUST = 7740.0;
// Vacuum thrust rating for attitude thrusters (Reaction Control System) [N]

const double ORBITER_MAIN_ISP0 = 5000.0;
const double ORBITER_MAIN_ISP1 = 4000.0;
// Vacuum and sea-level fuel-specific impulse for orbiter main engines [m/s]
// using H2/O2 (hydrogen/oxygen)
// The correct ISP0 is about 3532 m/s!

const double ORBITER_OMS_ISP0 = 3100;
const double ORBITER_OMS_ISP1 = 2500;
// Vacuum and sea-level fuel-specific impulse for Orbital Maneuvering System [m/s]
// using MMH/N2O4 (monomethyl hydrazine/nitrogen tetroxide)

const double ORBITER_RCS_ISP0 = 5000.0;
const double ORBITER_RCS_ISP1 = 4000.0;
// Vacuum and sea-level fuel-specific impulse for Reaction Control System [m/s]

const double GEAR_OPERATING_SPEED = 0.3;
// Opening/closing speed of landing gear (1/sec)
// => gear cycle ~ 3 sec

const double DOOR_OPERATING_SPEED = 0.007353;
// Opening/closing speed of payload bay doors (1/sec)
// This contains the door opening sequence (63 sec for each door) and an
// interval of 10 sec between the two door operations

const double RAD_OPERATING_SPEED = 0.025;
// Deployment/stowing speed of radiators (1/sec)
// => radiator cycle = 40 sec

const double RADLATCH_OPERATING_SPEED = 0.2;
// Release/engaging speed of radiator latches (1/sec)
// => radiator latch cycle = 5 sec

const double KU_OPERATING_SPEED = 0.0435;
// Deployment speed of the Ku Band antenna (1/sec)
// cycle is 23 sec

const double SPEEDBRAKE_OPERATING_SPEED = 0.20284;
// Deployment speed of the speedbrake (1/sec)
// cycle is 4.93 sec

const double ARM_OPERATING_SPEED = 0.005;
// RMS arm joint rotation speed (rad/sec)

const VECTOR3 ORBITER_CS = {234.8,389.1,68.2};
// Orbiter cross sections (projections into principal axes) [m^2]

const VECTOR3 ORBITER_CS_GEAR = {10.0,0.0,3.0};
// Contribution of fully extended landing gear to cross sections

const double ORBITER_CW[4] = {0.13, 0.5, 1.4, 1.4};
// Orbiter wind resistance coefficients in principal directions (+z, -z, +-x, +-y)

const double ORBITER_CW_GEAR[4] = {0.04, 0.04, 0.05, 0.0};
// Contribution of fully extended landing gear to wind resistance

const double MAX_GRAPPLING_DIST = 0.5;
// max distance between RMS tip and grappling point for successful grappling

// ==========================================================
// Some Tank-related parameters
// ==========================================================

const double TANK_MAX_PROPELLANT_MASS = 719115.0;
// Main tank propellant mass [kg]

const double TANK_EMPTY_MASS = 35425.0;
// Main tank empty mass

// ==========================================================
// Some SRB-related parameters
// ==========================================================

const double SRB_MAX_PROPELLANT_MASS = 502126.0;
// SRB propellant mass [kg]

const double SRB_EMPTY_MASS = 87543.0;
// SRB empty mass [kg]

const double SRB_ISP0 = 3574.68;
const double SRB_ISP1 = 2859.74;
// SRB vacuum and sea-level fuel-specific impulse [m/s]

const double SRB_THRUST = 1202020.0*9.81 * 1.25;
// Vacuum SRB thrust per unit [N]

const double MAX_ATT_LAUNCH = 1e5;
const double MAX_ROLL_SRB = 2.5e5;
// Max attitude thrust during launch phase (temporary)

const double SRB_STABILISATION_TIME = 4.0;
// MET: -SRB ignition

const double SRB_SEPARATION_TIME = 126.0;
// MET: SRB separation

const double SRB_CUTOUT_TIME = 135.0;
// MET: engine shutdown

// ==========================================================
// Mesh offsets for various configurations
// ==========================================================

const VECTOR3 OFS_ZERO             = { 0.0, 0.0,  0.0  };
const VECTOR3 OFS_LAUNCH_ORBITER   = { 0.0, 6.22,-7.795};
const VECTOR3 OFS_LAUNCH_TANK      = { 0.0,-1.91, 5.72 };
const VECTOR3 OFS_LAUNCH_RIGHTSRB  = { 6.2,-1.91,-5.68 };
const VECTOR3 OFS_LAUNCH_LEFTSRB   = {-6.2,-1.91,-5.68 };
const VECTOR3 OFS_WITHTANK_ORBITER = { 0.0, 4.79,-9.185};
const VECTOR3 OFS_WITHTANK_TANK    = { 0.0,-3.34, 4.33 };
const VECTOR3 ORBITER_DOCKPOS      = { 0.0, 2.44,10.44 };
const VECTOR3 OFS_MMU              = {0,2.44,10.44};

// ==========================================================
// Mesh group indices for some components
// ==========================================================

const UINT MESH_UNDEFINED = (UINT)-1;

// ==========================================================
// panel area identifiers
// ==========================================================

// define MFD function buttons
#define AID_CDR1_BUTTONS   1
#define AID_CDR2_BUTTONS   2
#define AID_PLT1_BUTTONS   3
#define AID_PLT2_BUTTONS   4
#define AID_MFD1_BUTTONS   5
#define AID_MFD2_BUTTONS   6
#define AID_MFD3_BUTTONS   7
#define AID_MFD4_BUTTONS   8
#define AID_MFD5_BUTTONS   9
#define AID_MFDA_BUTTONS  10
// D. Beachy: define power buttons
#define AID_CDR1_PWR      11
#define AID_CDR2_PWR      12
#define AID_PLT1_PWR      13
#define AID_PLT2_PWR      14
#define AID_MFD1_PWR      15
#define AID_MFD2_PWR      16
#define AID_MFD3_PWR      17
#define AID_MFD4_PWR      18
#define AID_MFD5_PWR      19
#define AID_MFDA_PWR      20
// MFD brightness buttons
#define AID_CDR1_BRT      21
#define AID_CDR2_BRT      22
#define AID_PLT1_BRT      23
#define AID_PLT2_BRT      24
#define AID_MFD1_BRT      25
#define AID_MFD2_BRT      26
#define AID_MFD3_BRT      27
#define AID_MFD4_BRT      28
#define AID_MFD5_BRT      29
#define AID_MFDA_BRT      30
// Panel R13L (payload bay operations)
#define AID_R13L_MIN     100
#define AID_R13L         100
#define AID_R13L_TKBK1   101
#define AID_R13L_TKBK2   102
#define AID_R13L_TKBK3   103
#define AID_R13L_TKBK4   104
#define AID_R13L_TKBK5   105
#define AID_R13L_TKBK6   106
#define AID_R13L_MAX     120

typedef struct {
    HINSTANCE hDLL;
    SURFHANDLE tkbk_label;
    HFONT font[1];
} GDIParams;

// ==========================================================
// Interface for derived vessel class: Atlantis
// ==========================================================

class Atlantis: public VESSEL3 {
    friend class PayloadBayOp;
    friend BOOL CALLBACK RMS_DlgProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
public:
    AnimState::Action gear_status, spdb_status;
    Atlantis (OBJHANDLE hObj, int fmodel);
    ~Atlantis();
    void SetLaunchConfiguration (void);
    void SetPostLaunchConfiguration (double srbtime);
    void SetOrbiterTankConfiguration (void);
    void SetOrbiterConfiguration (void);
    void DefineAnimations (void);
    void AddOrbiterVisual (const VECTOR3 &ofs);
    void AddTankVisual (const VECTOR3 &ofs);
    void AddSRBVisual (int which, const VECTOR3 &ofs);
    void SeparateBoosters (double srb_time);
    void SeparateTank (void);
    void SeparateMMU (void);
    void ToggleGrapple (void);
    void ToggleArrest (void);
    void SetGearParameters (double state);
    void Jettison ();
    void UpdateMesh ();
    void ClearMeshes ();
    void SetBayDoorPosition (double pos);
    void SetRadiatorPosition (double pos);
    void SetRadLatchPosition (double pos) {}
    void SetKuAntennaPosition (double pos);
    void OperateLandingGear (AnimState::Action action);
    void RevertLandingGear ();
    void OperateSpeedbrake (AnimState::Action action);
    void RevertSpeedbrake ();
    void SetAnimationArm (UINT anim, double state);

    void RegisterVC_CdrMFD ();
    void RegisterVC_PltMFD ();
    void RegisterVC_CntMFD ();
    void RegisterVC_AftMFD ();
    void RedrawPanel_MFDButton (SURFHANDLE surf, int mfd);

    int status; // 0=launch configuration
                // 1=SRB's engaged
                // 2=SRB's separated
                // 3=Tank separated (orbiter only)
    bool bManualSeparate; // flag for user-induced booster or tank separation

    double t0;          // reference time: designated liftoff time
    WORD srb_id1, srb_id2;

    double gear_proc; // landing gear deployment state (0=retracted, 1=deployed)
    //double kubd_proc; // Ku-band antenna deployment state (0=retracted, 1=deployed)
    double spdb_proc; // Speedbrake deployment state (0=retracted, 1=deployed)
    double ldoor_drag, rdoor_drag; // drag components from open cargo doors
    bool center_arm;
    bool arm_moved;
    double center_arm_t;
    bool do_eva;
    bool do_plat;
    bool do_cargostatic;
    VECTOR3 orbiter_ofs;
    VECTOR3 ofs_sts_sat;
    VECTOR3 cargo_static_ofs;
    VISHANDLE vis;      // handle for visual - note: we assume that only one visual per object is created!
    MESHHANDLE hOrbiterMesh, hOrbiterCockpitMesh, hOrbiterVCMesh, hTankMesh, hSRBMesh; // mesh handles
    char cargo_static_mesh_name[256];
    ATTACHMENTHANDLE sat_attach, rms_attach;
    VECTOR3 arm_tip[3];

    // Overloaded callback functions
    void clbkSetClassCaps (FILEHANDLE cfg);
    void clbkSetStateEx (const void *status);
    void clbkLoadStateEx (FILEHANDLE scn, void *vs);
    void clbkSaveState (FILEHANDLE scn);
    void clbkFocusChanged (bool getfocus, OBJHANDLE hNewVessel, OBJHANDLE hOldVessel);
    void clbkPostStep (double simt, double simdt, double mjd);
    bool clbkPlaybackEvent (double simt, double event_t, const char *event_type, const char *event);
    int  clbkConsumeBufferedKey (DWORD key, bool down, char *kstate);
    void clbkVisualCreated (VISHANDLE vis, int refcount);
    void clbkVisualDestroyed (VISHANDLE vis, int refcount);
    void clbkAnimate (double simt);
    void clbkMFDMode (int mfd, int mode);
    bool clbkLoadGenericCockpit ();
    bool clbkLoadVC (int id);
    bool clbkVCMouseEvent (int id, int event, VECTOR3 &p);
    bool clbkVCRedrawEvent (int id, int event, SURFHANDLE surf);
    bool clbkDrawHUD (int mode, const HUDPAINTSPEC *hps, oapi::Sketchpad *skp);

    PayloadBayOp *plop; // control and status of payload bay operations

private:
    void AutoMainGimbal();
    void LaunchClamps();
    void CreateAttControls (double th_pitch, double th_roll, double th_yaw, double isp0, double isp1);
    void CreateAttControls_Launch();
    void CreateAttControls_RCS();
    bool SatGrappled() const { return GetAttachmentStatus (rms_attach) != 0; }
    bool SatStowed() const { return GetAttachmentStatus (sat_attach) != 0; }
    ATTACHMENTHANDLE CanArrest() const;

    UINT anim_door;                            // handle for cargo door animation
    UINT anim_rad;                             // handle for radiator animation
    UINT anim_gear;                            // handle for landing gear animation
    UINT anim_kubd;                            // handle for Ku-band antenna animation
    UINT anim_elev;                            // handle for elevator animation
    UINT anim_laileron;                           // handle for left aileron animation
    UINT anim_raileron;                           // handle for right aileron animation
    UINT anim_rudder;                           // handle for rudder animation
    UINT anim_spdb;                            // handle for speed brake animation
    UINT mesh_orbiter;                         // index for orbiter mesh
    UINT mesh_cockpit;                         // index for cockpit mesh for external view
    UINT mesh_vc;                              // index for virtual cockpit mesh
    UINT mesh_tank;                            // index for external tank mesh
    UINT mesh_srb[2];                          // index for SRB meshes

    PROPELLANT_HANDLE ph_oms, ph_tank, ph_srb; // handles for propellant resources
    THRUSTER_HANDLE th_main[3];                // handles for orbiter main engines
    THRUSTER_HANDLE th_srb[2];                 // handles for SRB engines
    THGROUP_HANDLE thg_main, thg_srb;          // handles for thruster groups

    // RMS arm animation status
    ANIMATIONCOMPONENT_HANDLE hAC_arm, hAC_sat, hAC_satref;
    MGROUP_TRANSFORM *rms_anim[6];
    UINT anim_arm_sy, anim_arm_sp, anim_arm_ep, anim_arm_wp, anim_arm_wy, anim_arm_wr;
    double arm_sy, arm_sp, arm_ep, arm_wp, arm_wy, arm_wr;

    MGROUP_TRANSFORM *sat_anim, *sat_ref;

    bool reset_sat;
    OBJHANDLE hMMU, hSAT;
    bool render_cockpit;
    VCHUDSPEC huds;
    EXTMFDSPEC mfds[10];
    double mfdbright[10];

    LightEmitter * engine_light;
    double engine_light_level;
};

// ==========================================================
// Interface for derived vessel class: Atlantis_SRB
// ==========================================================

class Atlantis_SRB: public VESSEL2 {
public:
    Atlantis_SRB (OBJHANDLE hObj);
    // Construct interface from existing object

    void SetRefTime (void);

    // Overloaded callback functions
    void clbkSetClassCaps (FILEHANDLE cfg);
    void clbkPostStep (double simt, double simdt, double mjd);
    void clbkPostCreation ();

private:
    double t0;                  // reference time: liftoff
    double srb_separation_time; // simulation time at which SRB separation was initiated
    bool bMainEngine;           // main engine firing?
    bool bSeparationEngine;     // separation thrusters firing?
    MESHHANDLE hSRBMesh;
    PROPELLANT_HANDLE ph_main;  // handle for propellant resource
    THRUSTER_HANDLE th_main;    // engine handle
    THRUSTER_HANDLE th_bolt;    // separation bolt
};

// ==========================================================
// Interface for derived vessel class: Atlantis_Tank
// ==========================================================

class Atlantis_Tank: public VESSEL2 {
public:
    Atlantis_Tank (OBJHANDLE hObj);
    // Construct interface from existing object

    // Overloaded callback functions
    void clbkSetClassCaps (FILEHANDLE cfg);
    void clbkPostStep (double simt, double simdt, double mjd);

private:
    MESHHANDLE hTankMesh;
};

#endif // !__ATLANTIS_H

Atlantis.cpp :

Code:
// ==============================================================
//                 ORBITER MODULE: Atlantis
//                  Part of the ORBITER SDK
//          Copyright (C) 2001-2003 Martin Schweiger
//                   All rights reserved
//
// Atlantis.cpp
// Reference implementation of Atlantis (Space Shuttle) vessel
// class module
//
// RMS, grappling and MMU capabilities by Robert Conley
// ==============================================================

#define STRICT 1
#define ORBITER_MODULE
#include "Atlantis.h"
#include "PlBayOp.h"
#include "DlgCtrl.h"
#include "meshres.h"
#include "meshres_vc.h"
#include "resource.h"
#include <stdio.h>
#include <fstream>

#ifdef _DEBUG
    // D. Beachy: for BoundsChecker debugging
    extern int GrowStack();
#endif

#define LOADBMP(id) (LoadBitmap (g_Param.hDLL, MAKEINTRESOURCE (id)))

// ==============================================================
// Global (class-wide) parameters

GDIParams g_Param;

char *ActionString[5] = {"STOPPED", "ISCLOSED", "ISOPEN", "CLOSE", "OPEN"};

HELPCONTEXT g_hc = {
    "html/vessels/Atlantis.chm",
    0,
    "html/vessels/Atlantis.chm::/Atlantis.hhc",
    "html/vessels/Atlantis.chm::/Atlantis.hhk"
};


// ==============================================================
// Local prototypes

BOOL CALLBACK Atlantis_DlgProc (HWND, UINT, WPARAM, LPARAM);
BOOL CALLBACK RMS_DlgProc (HWND, UINT, WPARAM, LPARAM);
extern void GetSRB_State (double met, double &thrust_level, double &prop_level);

// ==============================================================
// Airfoil coefficient functions
// Return lift, moment and zero-lift drag coefficients as a
// function of angle of attack (alpha or beta)
// ==============================================================

// 1. vertical lift component (wings and body)

void VLiftCoeff (double aoa, double M, double Re, double *cl, double *cm, double *cd)
{
    static const double step = RAD*15.0;
    static const double istep = 1.0/step;
    static const int nabsc = 25;
    static const double CL[nabsc] = {0.1, 0.17, 0.2, 0.2, 0.17, 0.1, 0, -0.11, -0.24, -0.38,  -0.5,  -0.5, -0.02, 0.6355,    0.63,   0.46, 0.28, 0.13, 0.0, -0.16, -0.26, -0.29, -0.24, -0.1, 0.1};
    static const double CM[nabsc] = {  0,    0,   0,   0,    0,   0, 0,     0,    0,0.002,0.004, 0.0025,0.0012,      0,-0.0012,-0.0007,    0,    0,   0,     0,     0,     0,     0,    0,   0};
    // lift and moment coefficients from -180 to 180 in 15 degree steps.
    // This uses a documented lift slope of 0.0437/deg, everything else is rather ad-hoc

    aoa += PI;
    int idx = max (0, min (23, (int)(aoa*istep)));
    double d = aoa*istep - idx;
    *cl = CL[idx] + (CL[idx+1]-CL[idx])*d;
    *cm = CM[idx] + (CM[idx+1]-CM[idx])*d;
    *cd = 0.06 + oapiGetInducedDrag (*cl, 2.266, 0.6);
}

// 2. horizontal lift component (vertical stabiliser and body)

void HLiftCoeff (double beta, double M, double Re, double *cl, double *cm, double *cd)
{
    static const double step = RAD*22.5;
    static const double istep = 1.0/step;
    static const int nabsc = 17;
    static const double CL[nabsc] = {0, 0.2, 0.3, 0.2, 0, -0.2, -0.3, -0.2, 0, 0.2, 0.3, 0.2, 0, -0.2, -0.3, -0.2, 0};

    beta += PI;
    int idx = max (0, min (15, (int)(beta*istep)));
    double d = beta*istep - idx;
    *cl = CL[idx] + (CL[idx+1]-CL[idx])*d;
    *cm = 0.0;
    *cd = 0.02 + oapiGetInducedDrag (*cl, 1.5, 0.6);
}

// ==============================================================
// Specialised vessel class Atlantis
// ==============================================================

// --------------------------------------------------------------
// Constructor
// --------------------------------------------------------------
Atlantis::Atlantis (OBJHANDLE hObj, int fmodel)
: VESSEL3 (hObj, fmodel)
{
#ifdef _DEBUG
        // D. Beachy: for BoundsChecker debugging
        GrowStack();
#endif
    int i;

    plop            = new PayloadBayOp (this);
    status          = 3;
    gear_status     = AnimState::CLOSED;
    gear_proc       = 0.0;
    ldoor_drag      = rdoor_drag = 0.0;
    spdb_status     = AnimState::CLOSED;
    spdb_proc       = 0.0;

    engine_light_level = 0.0;
    COLOUR4 col_diff = {1,1,1,0};
    COLOUR4 col_zero = {0,0,0,0};
    engine_light = AddPointLight (_V(0,0,-25), 300, 2e-4, 0, 4e-4, col_diff, col_zero, col_zero);
    engine_light->SetIntensityRef (&engine_light_level);

    mesh_orbiter    = MESH_UNDEFINED;
    mesh_cockpit    = MESH_UNDEFINED;
    mesh_vc         = MESH_UNDEFINED;
    mesh_tank       = MESH_UNDEFINED;
    mesh_srb[0] = mesh_srb[1] = MESH_UNDEFINED;

    vis             = NULL;
    
    reset_sat       = false;
    render_cockpit  = false;

    int mfdgrp[10] = {
        GRP_CDR1_VC,GRP_CDR2_VC,GRP_PLT1_VC,GRP_PLT2_VC,
        GRP_MFD1_VC, GRP_MFD2_VC, GRP_MFD3_VC, GRP_MFD4_VC, GRP_MFD5_VC,
        GRP_MFD_aft_VC};
    for (i = 0; i < 10; i++) {
        mfds[i].ngroup   = mfdgrp[i];
        mfds[i].flag     = MFD_SHOWMODELABELS;
        mfds[i].nbt1     = 5;
        mfds[i].nbt2     = 0;
        mfds[i].bt_yofs  = 256/6;
        mfds[i].bt_ydist = 256/7;
    }
    for (i = 0; i < 10; i++)
        mfdbright[i] =  1.0;
    huds.ngroup       = GRP_VirtualHUD_VC;
    huds.size         = 0.176558;

    // propellant resources
    ph_oms          = NULL;
    ph_tank         = NULL;
    ph_srb          = NULL;
    thg_main        = NULL;
    thg_srb         = NULL;

    // preload meshes
    hOrbiterMesh        = oapiLoadMeshGlobal ("Atlantis\\Atlantis");
    hOrbiterCockpitMesh = oapiLoadMeshGlobal ("Atlantis\\AtlantisCockpit");
    hOrbiterVCMesh      = oapiLoadMeshGlobal ("Atlantis\\AtlantisVC");
    hTankMesh           = oapiLoadMeshGlobal ("Atlantis_tank");
    hSRBMesh            = oapiLoadMeshGlobal ("Atlantis_srb");


    DefineAnimations();
    center_arm      = false;
    arm_moved       = false;
    bManualSeparate = false;
    ofs_sts_sat     = _V(0,0,0);      
    do_eva          = false;
    do_plat         = false;
    do_cargostatic  = false;
    vis             = NULL;
    sat_attach      = NULL;
    rms_attach      = NULL;
    cargo_static_ofs   =_V(0,0,0);

    // default arm status: stowed
    arm_sy = 0.5;
    arm_sp = 0.0;
    arm_ep = 0.0;
    arm_wp = 0.5;
    arm_wy = 0.5;
    arm_wr = 0.5;
    arm_tip[0] = _V(-2.26,1.71,-6.5);
    arm_tip[1] = _V(-2.26,1.71,-7.5);
    arm_tip[2] = _V(-2.26,2.71,-6.5);
}

// --------------------------------------------------------------
// Destructor
// --------------------------------------------------------------
Atlantis::~Atlantis ()
{
    delete plop;
    int i;
    for (i = 0; i < 6; i++) delete rms_anim[i];
}

// --------------------------------------------------------------
// launch configuration setup (orbiter + tank + SRB)
// --------------------------------------------------------------
void Atlantis::SetLaunchConfiguration (void)
{
    int i;
    extern PARTICLESTREAMSPEC srb_contrail, srb_exhaust;

    // *********************** physical parameters *********************************

    SetSize (30.0);
    SetEmptyMass (ORBITER_EMPTY_MASS + TANK_EMPTY_MASS + 2*SRB_EMPTY_MASS);
    SetCW (0.2, 0.5, 1.5, 1.5);
    SetWingAspect (0.7);
    SetWingEffectiveness (2.5);
    SetCrossSections (_V(687.4,849.5,189.4));
    SetRotDrag (_V(0.7,0.1,0.3));
    SetPMI (_V(179.1,176.8,29.3));
    SetTrimScale (0.05);
    SetLiftCoeffFunc (0); // simplification: we assume no lift during launch phase
    SetTouchdownPoints (_V(0,-10,-53.3), _V(-7,7,-53.3), _V(7,7,-53.3));

    // ************************* propellant specs **********************************

    if (!ph_oms)  ph_oms  = CreatePropellantResource (ORBITER_MAX_PROPELLANT_MASS); // OMS propellant
    if (!ph_tank) ph_tank = CreatePropellantResource (TANK_MAX_PROPELLANT_MASS);    // main tank
    if (!ph_srb)  ph_srb  = CreatePropellantResource (SRB_MAX_PROPELLANT_MASS*2.0); // SRB's
    SetDefaultPropellantResource (ph_tank); // display main tank level in generic HUD

    // *********************** thruster definitions ********************************

    // The main and SRB thrusters are defined so as to minimise the angular momentum
    // when engaged at maximum thrust. Dynamic gimbaling is not required until the SRBs
    // start to lose power since the centre of gravity is assumed static.
    // However the resulting linear force vector has a component in +y ("up") direction

    ClearThrusterDefinitions();

    // orbiter main thrusters
    th_main[0] = CreateThruster (OFS_LAUNCH_ORBITER+_V(-1.6,-0.2,-16.0), _V( 0.04994,0.0,0.99875), ORBITER_MAIN_THRUST, ph_tank, ORBITER_MAIN_ISP0, ORBITER_MAIN_ISP1);
    th_main[1] = CreateThruster (OFS_LAUNCH_ORBITER+_V( 1.6,-0.2,-16.0), _V(-0.04994,0.0,0.99875), ORBITER_MAIN_THRUST, ph_tank, ORBITER_MAIN_ISP0, ORBITER_MAIN_ISP1);
    th_main[2] = CreateThruster (OFS_LAUNCH_ORBITER+_V( 0.0, 3.2,-15.5), _V( 0.0,-0.13,1), ORBITER_MAIN_THRUST, ph_tank, ORBITER_MAIN_ISP0, ORBITER_MAIN_ISP1);
    thg_main = CreateThrusterGroup (th_main, 3, THGROUP_MAIN);
    SURFHANDLE tex_main = oapiRegisterExhaustTexture ("Exhaust_atsme");
    for (i = 0; i < 3; i++) AddExhaust (th_main[i], 30.0, 2.0, tex_main);

    // SRBs
    th_srb[0] = CreateThruster (OFS_LAUNCH_RIGHTSRB+_V(0.0,0.0,-21.8), _V(0,0.023643,0.999720), SRB_THRUST, ph_srb, SRB_ISP0, SRB_ISP1);
    th_srb[1] = CreateThruster (OFS_LAUNCH_LEFTSRB +_V(0.0,0.0,-21.8), _V(0,0.023643,0.999720), SRB_THRUST, ph_srb, SRB_ISP0, SRB_ISP1);
    thg_srb = CreateThrusterGroup (th_srb, 2, THGROUP_USER);
    SURFHANDLE tex = oapiRegisterExhaustTexture ("Exhaust2");
    srb_exhaust.tex = oapiRegisterParticleTexture ("Contrail2");
    srb_contrail.tex = oapiRegisterParticleTexture ("Contrail4");
    for (i = 0; i < 2; i++) AddExhaust (th_srb[i], 16.0, 2.0, tex);
    AddExhaustStream (th_srb[0], OFS_LAUNCH_RIGHTSRB+_V(0,0,/*-30*/-50), &srb_contrail);
    AddExhaustStream (th_srb[1], OFS_LAUNCH_LEFTSRB+_V(0,0,/*-30*/-50), &srb_contrail);
    AddExhaustStream (th_srb[0], OFS_LAUNCH_RIGHTSRB+_V(0,0,-25), &srb_exhaust);
    AddExhaustStream (th_srb[1], OFS_LAUNCH_LEFTSRB+_V(0,0,-25), &srb_exhaust);

    // attitude - this is temporary
    // attitude adjustment during launch phase should really be done via SRB gimbaling
    CreateAttControls_Launch();

    // ************************* aerodynamics **************************************

    ClearVariableDragElements ();
    CreateVariableDragElement (&spdb_proc, 5, OFS_LAUNCH_ORBITER+_V(0, 7.5, -14)); // speedbrake drag
    CreateVariableDragElement (&gear_proc, 2, OFS_LAUNCH_ORBITER+_V(0,-3,0));      // landing gear drag
    CreateVariableDragElement (&rdoor_drag, 7, OFS_LAUNCH_ORBITER+_V(2.9,0,10));   // right cargo door drag
    CreateVariableDragElement (&ldoor_drag, 7, OFS_LAUNCH_ORBITER+_V(-2.9,0,10));  // right cargo door drag

    // ************************ visual parameters **********************************

    AddOrbiterVisual (OFS_LAUNCH_ORBITER);
    AddTankVisual    (OFS_LAUNCH_TANK);
    AddSRBVisual     (0, OFS_LAUNCH_RIGHTSRB);
    AddSRBVisual     (1, OFS_LAUNCH_LEFTSRB);

    status = 0;
}

// --------------------------------------------------------------
// Configuration after launch, before SRB separation
// --------------------------------------------------------------
void Atlantis::SetPostLaunchConfiguration (double met)
{
    SetLaunchConfiguration();
    t0 = -met; // reference time (liftoff)
    status = 1;
}

// --------------------------------------------------------------
// Configuration after booster separation (orbiter + tank)
// --------------------------------------------------------------
void Atlantis::SetOrbiterTankConfiguration (void)
{
    int i;
    VECTOR3 ofs;
    
    // *********************** physical parameters *********************************

    SetSize (28.8);
    SetEmptyMass (ORBITER_EMPTY_MASS + TANK_EMPTY_MASS);
    SetCW (0.2, 0.5, 1.5, 1.5);
    SetWingAspect (0.7);
    SetWingEffectiveness (2.5);
    SetCrossSections (_V(646.1,603.0,141.5));
    SetRotDrag (_V(0.7,0.1,0.3));
    SetPMI (_V(168.2,154.1,24.2));
    SetTrimScale (0.05);
    SetLiftCoeffFunc (0); // simplification: we assume no lift during launch phase
    SetTouchdownPoints (_V(0,-5,30), _V(-10,-10,-30), _V(10,0,-30));

    // ************************* propellant specs **********************************

    if (!ph_oms)  ph_oms  = CreatePropellantResource (ORBITER_MAX_PROPELLANT_MASS); // OMS propellant
    if (!ph_tank) ph_tank = CreatePropellantResource (TANK_MAX_PROPELLANT_MASS);    // main tank
    SetDefaultPropellantResource (ph_tank); // display main tank level in generic HUD

    // *********************** thruster definitions ********************************

    // Orbiter main engines
    // The thruster directions are adjusted so as to generate no angular moment
    // i.e. sum_{i=1}^3 thrusterpos_i x thrusterdir_i = 0
    // This assumes that all 3 main engines generate the same amount of thrust at
    // all times

    ofs = OFS_WITHTANK_ORBITER;
    if (thg_main) { // main engines already defined - just modify parameters
        SetThrusterRef (th_main[0], ofs+_V(-1.6,-0.2,-16.0));
        SetThrusterDir (th_main[0], _V( 0.0624,-0.1789,0.9819));
        SetThrusterRef (th_main[1], ofs+_V( 1.6,-0.2,-16.0));
        SetThrusterDir (th_main[1], _V(-0.0624,-0.1789,0.9819));
        SetThrusterRef (th_main[2], ofs+_V( 0.0, 3.2,-15.5));
        SetThrusterDir (th_main[2], _V( 0.0,   -0.308046,0.951372));
    } else {        // create main engines
        th_main[0] = CreateThruster (ofs+_V(-1.6,-0.2,-16.0), _V( 0.0624,-0.1789,0.9819), ORBITER_MAIN_THRUST, ph_tank, ORBITER_MAIN_ISP0, ORBITER_MAIN_ISP1);
        th_main[1] = CreateThruster (ofs+_V( 1.6,-0.2,-16.0), _V(-0.0624,-0.1789,0.9819), ORBITER_MAIN_THRUST, ph_tank, ORBITER_MAIN_ISP0, ORBITER_MAIN_ISP1);
        th_main[2] = CreateThruster (ofs+_V( 0.0, 3.2,-15.5), _V( 0.0,   -0.308046,0.951372), ORBITER_MAIN_THRUST, ph_tank, ORBITER_MAIN_ISP0, ORBITER_MAIN_ISP1);
        thg_main = CreateThrusterGroup (th_main, 3, THGROUP_MAIN);
        SURFHANDLE tex_main = oapiRegisterExhaustTexture ("Exhaust_atsme");
        for (i = 0; i < 3; i++) AddExhaust (th_main[i], 30.0, 2.0, tex_main);
    }
    if (!ThrusterGroupDefined (THGROUP_ATT_PITCHUP))
        CreateAttControls_Launch();

    // ************************* aerodynamics **************************************

    ClearVariableDragElements ();
    CreateVariableDragElement (&spdb_proc, 5, ofs+_V(0, 7.5, -14)); // speedbrake drag
    CreateVariableDragElement (&gear_proc, 2, ofs+_V(0,-3,0));      // landing gear drag
    CreateVariableDragElement (&rdoor_drag, 7, ofs+_V(2.9,0,10));   // right cargo door drag
    CreateVariableDragElement (&ldoor_drag, 7, ofs+_V(-2.9,0,10));  // right cargo door drag

    // ************************ visual parameters **********************************

    AddOrbiterVisual (OFS_WITHTANK_ORBITER);
    AddTankVisual    (OFS_WITHTANK_TANK);

    status = 2;
}

// --------------------------------------------------------------
// Configuration after tank separation (orbiter only)
// --------------------------------------------------------------
void Atlantis::SetOrbiterConfiguration (void)
{
    int i;

    // *********************** physical parameters *********************************

    SetSize (19.6);
    SetEmptyMass (ORBITER_EMPTY_MASS);
    VECTOR3 r[2] = {{0,0,10},{0,0,-8}};
    SetPMI (_V(78.2,82.1,10.7));
    SetGravityGradientDamping (20.0);
    SetTrimScale (0.05);
    SetCW (ORBITER_CW[0], ORBITER_CW[1], ORBITER_CW[2], ORBITER_CW[3]);
    SetCrossSections (ORBITER_CS);
    SetGearParameters (gear_proc);

    if (GetFlightModel() >= 1) { // realistic flight model
    //    SetPitchMomentScale (1e-5);
    //    SetYawMomentScale (2e-5);
        if (gear_status == AnimState::OPEN) {
            SetCW (ORBITER_CW[0]+ORBITER_CW_GEAR[0],
                   ORBITER_CW[1]+ORBITER_CW_GEAR[1],
                   ORBITER_CW[2]+ORBITER_CW_GEAR[2],
                   ORBITER_CW[3]+ORBITER_CW_GEAR[3]);
            SetCrossSections (ORBITER_CS+ORBITER_CS_GEAR);
        }
    }

    // ************************* aerodynamics **************************************

    SetRotDrag (_V(0.43,0.43,0.29)); // angular drag

    CreateAirfoil (LIFT_VERTICAL,   _V(0,0,-0.5), VLiftCoeff, 20, 270, 2.266);
    CreateAirfoil (LIFT_HORIZONTAL, _V(0,0,-4), HLiftCoeff, 20,  50, 1.5);

    CreateControlSurface (AIRCTRL_ELEVATOR, 5.0, 1.5, _V( 0, 0,  -15), AIRCTRL_AXIS_XPOS, anim_elev);
    CreateControlSurface (AIRCTRL_RUDDER,   2.0, 1.5, _V( 0, 3,  -16), AIRCTRL_AXIS_YPOS, anim_rudder);
    CreateControlSurface (AIRCTRL_AILERON,  3.0, 1.5, _V( 7,-0.5,-15), AIRCTRL_AXIS_XPOS, anim_raileron);
    CreateControlSurface (AIRCTRL_AILERON,  3.0, 1.5, _V(-7,-0.5,-15), AIRCTRL_AXIS_XNEG, anim_laileron);

    ClearVariableDragElements ();
    CreateVariableDragElement (&spdb_proc, 5, _V(0, 7.5, -14)); // speedbrake drag
    CreateVariableDragElement (&gear_proc, 2, _V(0,-3,0));      // landing gear drag
    CreateVariableDragElement (&rdoor_drag, 7, _V(2.9,0,10));   // right cargo door drag
    CreateVariableDragElement (&ldoor_drag, 7, _V(-2.9,0,10));  // right cargo door drag

    SetADCtrlMode (7);

    // ************************* particle streams **********************************

    PARTICLESTREAMSPEC rps = {
        0, 20, 20, 0, 0.03, 0.5, 100, 3, PARTICLESTREAMSPEC::DIFFUSE,
        PARTICLESTREAMSPEC::LVL_FLAT, 1, 1, PARTICLESTREAMSPEC::ATM_PLIN, 6e7, 12e7
    };
    AddReentryStream (&rps);

    // ************************* propellant specs **********************************

    if (!ph_oms) ph_oms  = CreatePropellantResource (ORBITER_MAX_PROPELLANT_MASS); // OMS propellant
    SetDefaultPropellantResource (ph_oms); // display OMS tank level in generic HUD

    // *********************** thruster definitions ********************************

    // OMS (Orbital Manouevering System)
    th_main[0] = CreateThruster (_V(-2.7,3.7,-13.8), _V( 0.03,-0.25885,0.96545), ORBITER_OMS_THRUST, ph_oms, ORBITER_OMS_ISP0, ORBITER_OMS_ISP1);
    th_main[1] = CreateThruster (_V( 2.7,3.7,-13.8), _V(-0.03,-0.25885,0.96545), ORBITER_OMS_THRUST, ph_oms, ORBITER_OMS_ISP0, ORBITER_OMS_ISP1);
    thg_main = CreateThrusterGroup (th_main, 2, THGROUP_MAIN);
    for (i = 0; i < 2; i++) AddExhaust (th_main[i], 4.0, 0.5);

    // RCS (Reaction Control System)
    CreateAttControls_RCS ();

    // ************************ visual parameters **********************************

    AddOrbiterVisual (OFS_ZERO);

    status = 3;
}

// --------------------------------------------------------------
// Generic attitude controls
// --------------------------------------------------------------
void Atlantis::CreateAttControls (double th_pitch, double th_roll, double th_yaw, double isp0, double isp1)
{
    THRUSTER_HANDLE hAtt[2];
    hAtt[0] = CreateThruster (_V(0,0, 25), _V(0, 1,0), th_pitch, ph_tank, isp0, isp1);
    hAtt[1] = CreateThruster (_V(0,0,-25), _V(0,-1,0), th_pitch, ph_tank, isp0, isp1);
    CreateThrusterGroup (hAtt, 2, THGROUP_ATT_PITCHUP);
    hAtt[0] = CreateThruster (_V(0,0, 25), _V(0,-1,0), th_pitch, ph_tank, isp0, isp1);
    hAtt[1] = CreateThruster (_V(0,0,-25), _V(0, 1,0), th_pitch, ph_tank, isp0, isp1);
    CreateThrusterGroup (hAtt, 2, THGROUP_ATT_PITCHDOWN);
    hAtt[0] = CreateThruster (_V( 8,0, 0), _V(0, 1,0), th_roll,  ph_tank, isp0, isp1);
    hAtt[1] = CreateThruster (_V(-8,0, 0), _V(0,-1,0), th_roll,  ph_tank, isp0, isp1);
    CreateThrusterGroup (hAtt, 2, THGROUP_ATT_BANKLEFT);
    hAtt[0] = CreateThruster (_V( 8,0, 0), _V(0,-1,0), th_roll,  ph_tank, isp0, isp1);
    hAtt[1] = CreateThruster (_V(-8,0, 0), _V(0, 1,0), th_roll,  ph_tank, isp0, isp1);
    CreateThrusterGroup (hAtt, 2, THGROUP_ATT_BANKRIGHT);
    hAtt[0] = CreateThruster (_V(0,0, 10), _V( 1,0,0), th_yaw,   ph_tank, isp0, isp1);
    hAtt[1] = CreateThruster (_V(0,0,-10), _V(-1,0,0), th_yaw,   ph_tank, isp0, isp1);
    CreateThrusterGroup (hAtt, 2, THGROUP_ATT_YAWRIGHT);
    hAtt[0] = CreateThruster (_V(0,0, 10), _V(-1,0,0), th_yaw,   ph_tank, isp0, isp1);
    hAtt[1] = CreateThruster (_V(0,0,-10), _V( 1,0,0), th_yaw,   ph_tank, isp0, isp1);
    CreateThrusterGroup (hAtt, 2, THGROUP_ATT_YAWLEFT);
}

// --------------------------------------------------------------
// Attitude controls during launch phase
// --------------------------------------------------------------
void Atlantis::CreateAttControls_Launch()
{
    // we should only require pitch and bank, but yaw is also defined to make KILLROT work
    // no linear attitude modes are defined at this stage
    CreateAttControls (MAX_ATT_LAUNCH, MAX_ROLL_SRB, MAX_ATT_LAUNCH, ORBITER_MAIN_ISP0, ORBITER_MAIN_ISP1);
}

// --------------------------------------------------------------
// Attitude controls (RCS) during orbital phase
// --------------------------------------------------------------
void Atlantis::CreateAttControls_RCS()
{
    SURFHANDLE tex_rcs = oapiRegisterExhaustTexture ("Exhaust_atrcs");
    const double eh = 6.0;             // exhaust length scale
    const double ew1 = 0.4, ew2 = 0.8; // exhaust width scales

    // set of attitude thrusters (idealised). The arrangement is such that no angular
    // momentum is created in linear mode, and no linear momentum is created in rotational mode.
    THRUSTER_HANDLE th_att_rot[4], th_att_lin[4];
    th_att_rot[0] = th_att_lin[0] = CreateThruster (_V(0,0, 15.5), _V(0, 1,0), ORBITER_RCS_THRUST, ph_oms, ORBITER_RCS_ISP0, ORBITER_RCS_ISP1);
    th_att_rot[1] = th_att_lin[3] = CreateThruster (_V(0,0,-15.5), _V(0,-1,0), ORBITER_RCS_THRUST, ph_oms, ORBITER_RCS_ISP0, ORBITER_RCS_ISP1);
    th_att_rot[2] = th_att_lin[2] = CreateThruster (_V(0,0, 15.5), _V(0,-1,0), ORBITER_RCS_THRUST, ph_oms, ORBITER_RCS_ISP0, ORBITER_RCS_ISP1);
    th_att_rot[3] = th_att_lin[1] = CreateThruster (_V(0,0,-15.5), _V(0, 1,0), ORBITER_RCS_THRUST, ph_oms, ORBITER_RCS_ISP0, ORBITER_RCS_ISP1);
    CreateThrusterGroup (th_att_rot,   2, THGROUP_ATT_PITCHUP);
    CreateThrusterGroup (th_att_rot+2, 2, THGROUP_ATT_PITCHDOWN);
    CreateThrusterGroup (th_att_lin,   2, THGROUP_ATT_UP);
    CreateThrusterGroup (th_att_lin+2, 2, THGROUP_ATT_DOWN);

    AddExhaust (th_att_rot[0], eh, ew1, _V( 1.60,-0.20, 18.78), _V( 0.4339,-0.8830,-0.1793), tex_rcs);//F2D
    AddExhaust (th_att_rot[0], eh, ew1, _V( 1.68,-0.18, 18.40), _V( 0.4339,-0.8830,-0.1793), tex_rcs);//F4D
    AddExhaust (th_att_rot[0], eh, ew1, _V(-1.55,-0.20, 18.78), _V(-0.4339,-0.8830,-0.1793), tex_rcs);//F1D
    AddExhaust (th_att_rot[0], eh, ew1, _V(-1.63,-0.18, 18.40), _V(-0.4339,-0.8830,-0.1793), tex_rcs);//F3D

    AddExhaust (th_att_rot[1], eh, ew1, _V(-3.46, 3.20,-12.30), _V(0, 1,0), tex_rcs);//L4U
    AddExhaust (th_att_rot[1], eh, ew1, _V(-3.46, 3.20,-12.70), _V(0, 1,0), tex_rcs);//L2U
    AddExhaust (th_att_rot[1], eh, ew1, _V(-3.46, 3.20,-13.10), _V(0, 1,0), tex_rcs);//L1U

    AddExhaust (th_att_rot[1], eh, ew1, _V( 3.43, 3.20,-12.30), _V(0, 1,0), tex_rcs);//R4U
    AddExhaust (th_att_rot[1], eh, ew1, _V( 3.43, 3.20,-12.70), _V(0, 1,0), tex_rcs);//R2U
    AddExhaust (th_att_rot[1], eh, ew1, _V( 3.43, 3.20,-13.10), _V(0, 1,0), tex_rcs);//R1U

    AddExhaust (th_att_rot[2], eh, ew1, _V(-0.4 , 1.10, 18.3 ), _V(0, 1,0), tex_rcs);//F1U    
    AddExhaust (th_att_rot[2], eh, ew1, _V( 0.0 , 1.15 ,18.3 ), _V(0, 1,0), tex_rcs);//F3U
    AddExhaust (th_att_rot[2], eh, ew1, _V( 0.4 , 1.10, 18.3 ), _V(0, 1,0), tex_rcs);//F2U

    AddExhaust (th_att_rot[3], eh, ew1, _V(-3.1 , 1.55,-12.45), _V(-0.2844,-0.9481,-0.1422), tex_rcs);//L4D
    AddExhaust (th_att_rot[3], eh, ew1, _V(-3.1 , 1.6 ,-12.8 ), _V(-0.2844,-0.9481,-0.1422), tex_rcs);//L2D
    AddExhaust (th_att_rot[3], eh, ew1, _V(-3.1 , 1.65,-13.15), _V(-0.2844,-0.9481,-0.1422), tex_rcs);//L3D

    AddExhaust (th_att_rot[3], eh, ew1, _V( 3.15, 1.55,-12.45), _V( 0.2844,-0.9481,-0.1422), tex_rcs);//R4D
    AddExhaust (th_att_rot[3], eh, ew1, _V( 3.15, 1.6 ,-12.8 ), _V( 0.2844,-0.9481,-0.1422), tex_rcs);//R2D
    AddExhaust (th_att_rot[3], eh, ew1, _V( 3.15, 1.65,-13.15), _V( 0.2844,-0.9481,-0.1422), tex_rcs);//R3D

    th_att_rot[0] = th_att_lin[0] = CreateThruster (_V(0,0, 15.5), _V(-1,0,0), ORBITER_RCS_THRUST, ph_oms, ORBITER_RCS_ISP0, ORBITER_RCS_ISP1);
    th_att_rot[1] = th_att_lin[3] = CreateThruster (_V(0,0,-15.5), _V( 1,0,0), ORBITER_RCS_THRUST, ph_oms, ORBITER_RCS_ISP0, ORBITER_RCS_ISP1);
    th_att_rot[2] = th_att_lin[2] = CreateThruster (_V(0,0, 15.5), _V( 1,0,0), ORBITER_RCS_THRUST, ph_oms, ORBITER_RCS_ISP0, ORBITER_RCS_ISP1);
    th_att_rot[3] = th_att_lin[1] = CreateThruster (_V(0,0,-15.5), _V(-1,0,0), ORBITER_RCS_THRUST, ph_oms, ORBITER_RCS_ISP0, ORBITER_RCS_ISP1);
    CreateThrusterGroup (th_att_rot,   2, THGROUP_ATT_YAWLEFT);
    CreateThrusterGroup (th_att_rot+2, 2, THGROUP_ATT_YAWRIGHT);
    CreateThrusterGroup (th_att_lin,   2, THGROUP_ATT_LEFT);
    CreateThrusterGroup (th_att_lin+2, 2, THGROUP_ATT_RIGHT);

    AddExhaust (th_att_rot[0], eh, ew2, _V( 1.8 ,-0.3 , 18.0 ), _V( 1,0,0), tex_rcs);//F4R
    AddExhaust (th_att_rot[0], eh, ew2, _V( 1.75, 0.1 , 18.05), _V( 1,0,0), tex_rcs);//F2R
    AddExhaust (th_att_rot[2], eh, ew2, _V(-1.7 ,-0.3 , 18.0 ), _V(-1,0,0), tex_rcs);//F1L
    AddExhaust (th_att_rot[2], eh, ew2, _V(-1.65,-0.1 , 18.05), _V(-1,0,0), tex_rcs);//F3L

    AddExhaust (th_att_rot[1], eh, ew2, _V(-4.0 , 2.35,-12.35), _V(-1,0,0), tex_rcs);//L4L
    AddExhaust (th_att_rot[1], eh, ew2, _V(-4.0 , 2.35,-12.6 ), _V(-1,0,0), tex_rcs);//L2L
    AddExhaust (th_att_rot[1], eh, ew2, _V(-4.0 , 2.35,-13.0 ), _V(-1,0,0), tex_rcs);//L3L
    AddExhaust (th_att_rot[1], eh, ew2, _V(-4.0 , 2.35,-13.35), _V(-1,0,0), tex_rcs);//L1L

    AddExhaust (th_att_rot[3], eh, ew2, _V( 4.0 , 2.35,-12.35), _V( 1,0,0), tex_rcs);//R4R
    AddExhaust (th_att_rot[3], eh, ew2, _V( 4.0 , 2.35,-12.6 ), _V( 1,0,0), tex_rcs);//R2R
    AddExhaust (th_att_rot[3], eh, ew2, _V( 4.0 , 2.35,-13.0 ), _V( 1,0,0), tex_rcs);//R3R
    AddExhaust (th_att_rot[3], eh, ew2, _V( 4.0,  2.35,-13.35), _V( 1,0,0), tex_rcs);//R1R

    th_att_rot[0] = CreateThruster (_V( 2.7,0,0), _V(0, 1,0), ORBITER_RCS_THRUST, ph_oms, ORBITER_RCS_ISP0, ORBITER_RCS_ISP1);
    th_att_rot[1] = CreateThruster (_V(-2.7,0,0), _V(0,-1,0), ORBITER_RCS_THRUST, ph_oms, ORBITER_RCS_ISP0, ORBITER_RCS_ISP1);
    th_att_rot[2] = CreateThruster (_V(-2.7,0,0), _V(0, 1,0), ORBITER_RCS_THRUST, ph_oms, ORBITER_RCS_ISP0, ORBITER_RCS_ISP1);
    th_att_rot[3] = CreateThruster (_V( 2.7,0,0), _V(0,-1,0), ORBITER_RCS_THRUST, ph_oms, ORBITER_RCS_ISP0, ORBITER_RCS_ISP1);
    CreateThrusterGroup (th_att_rot,   2, THGROUP_ATT_BANKLEFT);
    CreateThrusterGroup (th_att_rot+2, 2, THGROUP_ATT_BANKRIGHT);

    AddExhaust (th_att_rot[0], eh, ew1, _V( 1.60,-0.20, 18.78), _V( 0.4339,-0.8830,-0.1793), tex_rcs);//F2D
    AddExhaust (th_att_rot[0], eh, ew1, _V( 1.68,-0.18, 18.40), _V( 0.4339,-0.8830,-0.1793), tex_rcs);//F4D
    AddExhaust (th_att_rot[2], eh, ew1, _V(-1.55,-0.20, 18.78), _V(-0.4339,-0.8830,-0.1793), tex_rcs);//F1D
    AddExhaust (th_att_rot[2], eh, ew1, _V(-1.63,-0.18, 18.40), _V(-0.4339,-0.8830,-0.1793), tex_rcs);//F3D

    AddExhaust (th_att_rot[1], eh, ew1, _V(-3.46, 3.20,-12.30), _V(0, 1,0), tex_rcs);//L4U
    AddExhaust (th_att_rot[1], eh, ew1, _V(-3.46, 3.20,-12.70), _V(0, 1,0), tex_rcs);//L2U
    AddExhaust (th_att_rot[1], eh, ew1, _V(-3.46, 3.20,-13.10), _V(0, 1,0), tex_rcs);//L1U

    AddExhaust (th_att_rot[3], eh, ew1, _V( 3.43, 3.20,-12.30), _V(0, 1,0), tex_rcs);//R4U
    AddExhaust (th_att_rot[3], eh, ew1, _V( 3.43, 3.20,-12.70), _V(0, 1,0), tex_rcs);//R2U
    AddExhaust (th_att_rot[3], eh, ew1, _V( 3.43, 3.20,-13.10), _V(0, 1,0), tex_rcs);//R1U

    AddExhaust (th_att_rot[2], eh, ew1, _V(-3.1 , 1.55,-12.45), _V(-0.2844,-0.9481,-0.1422), tex_rcs);//L4D
    AddExhaust (th_att_rot[2], eh, ew1, _V(-3.1 , 1.6 ,-12.8 ), _V(-0.2844,-0.9481,-0.1422), tex_rcs);//L2D
    AddExhaust (th_att_rot[2], eh, ew1, _V(-3.1 , 1.65,-13.15), _V(-0.2844,-0.9481,-0.1422), tex_rcs);//L3D

    AddExhaust (th_att_rot[0], eh, ew1, _V( 3.15, 1.55,-12.45), _V( 0.2844,-0.9481,-0.1422), tex_rcs);//R4D
    AddExhaust (th_att_rot[0], eh, ew1, _V( 3.15, 1.6 ,-12.8 ), _V( 0.2844,-0.9481,-0.1422), tex_rcs);//R2D
    AddExhaust (th_att_rot[0], eh, ew1, _V( 3.15, 1.65,-13.15), _V( 0.2844,-0.9481,-0.1422), tex_rcs);//R3D

    th_att_lin[0] = CreateThruster (_V(0,0,-16), _V(0,0, 1), ORBITER_RCS_THRUST, ph_oms, ORBITER_RCS_ISP0, ORBITER_RCS_ISP1);
    th_att_lin[1] = CreateThruster (_V(0,0, 16), _V(0,0,-1), ORBITER_RCS_THRUST, ph_oms, ORBITER_RCS_ISP0, ORBITER_RCS_ISP1);
    CreateThrusterGroup (th_att_lin,   1, THGROUP_ATT_FORWARD);
    CreateThrusterGroup (th_att_lin+1, 1, THGROUP_ATT_BACK);

    AddExhaust (th_att_lin[0], eh, ew1, _V(-3.59, 2.8 ,-13.6 ), _V(0,0,-1), tex_rcs);//L1A
    AddExhaust (th_att_lin[0], eh, ew1, _V(-3.27, 2.8 ,-13.6 ), _V(0,0,-1), tex_rcs);//L3A
    AddExhaust (th_att_lin[0], eh, ew1, _V( 3.64, 2.8 ,-13.6 ), _V(0,0,-1), tex_rcs);//R1A
    AddExhaust (th_att_lin[0], eh, ew1, _V( 3.27, 2.8 ,-13.6 ), _V(0,0,-1), tex_rcs);//R3A

    AddExhaust (th_att_lin[1], eh, ew1, _V( 0.0 , 0.75, 19.2 ), _V(0, 0.0499, 0.9988), tex_rcs);//F3F
    AddExhaust (th_att_lin[1], eh, ew1, _V(-0.4 , 0.7 , 19.2 ), _V(0, 0.0499, 0.9988), tex_rcs);//F1F
    AddExhaust (th_att_lin[1], eh, ew1, _V( 0.4 , 0.7 , 19.2 ), _V(0, 0.0499, 0.9988), tex_rcs);//F2F
}

// --------------------------------------------------------------
// Define animation sequences for moving parts
// --------------------------------------------------------------
void Atlantis::DefineAnimations (void)
{
    UINT midx = 1; // mesh index for all external animations
    UINT vidx = 2; // mesh index for all VC animations

    // ***** 1. Cargo door and radiator animations *****

    static UINT RCargoDoorGrp[4] = {GRP_cargodooroutR,GRP_cargodoorinR,GRP_radiatorFR,GRP_radiatorBR};
    static MGROUP_ROTATE RCargoDoor (midx, RCargoDoorGrp, 4,
        _V(2.88, 1.3, 0), _V(0,0,1), (float)(-175.5*RAD));
    static UINT LCargoDoorGrp[4] = {GRP_cargodooroutL,GRP_cargodoorinL,GRP_radiatorFL,GRP_radiatorBL};
    static MGROUP_ROTATE LCargoDoor (midx, LCargoDoorGrp, 4,
        _V(-2.88, 1.3, 0), _V(0,0,1), (float)(175.5*RAD));

    anim_door = CreateAnimation (0);
    AddAnimationComponent (anim_door, 0.0, 0.4632, &RCargoDoor);
    AddAnimationComponent (anim_door, 0.5368, 1.0, &LCargoDoor);

    static UINT RRadiatorGrp[1] = {GRP_radiatorFR};
    static MGROUP_ROTATE RRadiator (midx, RRadiatorGrp, 1,
        _V(2.88, 1.3, 0), _V(0,0,1), (float)(35.5*RAD));
    static UINT LRadiatorGrp[1] = {GRP_radiatorFL};
    static MGROUP_ROTATE LRadiator (midx, LRadiatorGrp, 1,
        _V(-2.88, 1.3, 0), _V(0,0,1), (float)(-35.5*RAD));

    anim_rad = CreateAnimation (0);
    AddAnimationComponent (anim_rad, 0, 1, &RRadiator);
    AddAnimationComponent (anim_rad, 0, 1, &LRadiator);

    // ***** 2. Landing gear animation *****

    static UINT LNosewheelDoorGrp[1] = {GRP_nosedoorL};
    static MGROUP_ROTATE LNosewheelDoor (midx, LNosewheelDoorGrp, 1,
        _V(-0.78, -2.15, 17), _V(0, 0.195, 0.981), (float)(-60.0*RAD));
    static UINT RNosewheelDoorGrp[1] = {GRP_nosedoorR};
    static MGROUP_ROTATE RNosewheelDoor (midx, RNosewheelDoorGrp, 1,
        _V(0.78, -2.15, 17), _V(0, 0.195, 0.981), (float)(60.0*RAD));
    static UINT NosewheelGrp[2] = {GRP_nosewheel,GRP_nosegear};
    static MGROUP_ROTATE Nosewheel (midx, NosewheelGrp, 2,
        _V(0.0, -2.01, 18.11), _V(1, 0, 0), (float)(94.5*RAD));
    static UINT RGearDoorGrp[1] = {GRP_geardoorR};
    static MGROUP_ROTATE RGearDoor (midx, RGearDoorGrp, 1,
        _V(4.35, -2.64, -1.69), _V(0, 0.02, 0.9), (float)(96.2*RAD));
    static UINT LGearDoorGrp[1] = {GRP_geardoorL};
    static MGROUP_ROTATE LGearDoor (midx, LGearDoorGrp, 1,
        _V(-4.35, -2.64, -1.69), _V(0, 0.02, 0.9), (float)(-96.2*RAD));
    static UINT MainGearGrp[4] = {GRP_wheelR,GRP_gearR,GRP_wheelL,GRP_gearL};
    static MGROUP_ROTATE MainGear (midx, MainGearGrp, 4,
        _V(0, -2.66, -3.68), _V(1, 0, 0), (float)(94.5*RAD));

    anim_gear = CreateAnimation (0);
    AddAnimationComponent (anim_gear, 0,   0.5, &LNosewheelDoor);
    AddAnimationComponent (anim_gear, 0,   0.5, &RNosewheelDoor);
    AddAnimationComponent (anim_gear, 0.4, 1.0, &Nosewheel);
    AddAnimationComponent (anim_gear, 0,   0.5, &RGearDoor);
    AddAnimationComponent (anim_gear, 0,   0.5, &LGearDoor);
    AddAnimationComponent (anim_gear, 0.4, 1.0, &MainGear);

    // ***** 3. Ku-band antenna animation *****

    static UINT KuBand1Grp[3] = {GRP_startrackers,GRP_KUband1,GRP_KUband2};
    static MGROUP_ROTATE KuBand1 (midx, KuBand1Grp, 3,
        _V(2.85, 0.85, 0), _V(0,0,1), (float)(-18*RAD));
    static UINT KuBand2Grp[1] = {GRP_KUband2};
    static MGROUP_ROTATE KuBand2 (midx, KuBand2Grp, 1,
        _V(2.78, 1.7, 0), _V(0,0,1), (float)(-90*RAD));
    static UINT KuBand3Grp[2] = {GRP_KUband1,GRP_KUband2};
    static MGROUP_ROTATE KuBand3 (midx, KuBand3Grp, 2,
        _V(2.75, 2.05, 11.47), _V(0,1,0), (float)(-113*RAD));

    anim_kubd = CreateAnimation (0);
    AddAnimationComponent (anim_kubd, 0,     0.333, &KuBand1);
    AddAnimationComponent (anim_kubd, 0.333, 0.667, &KuBand2);
    AddAnimationComponent (anim_kubd, 0.667, 0.999, &KuBand3);

    // ***** 4. Elevator animation of elevons *****

    static UINT ElevGrp[4] = {GRP_flapR,GRP_flapL,GRP_aileronL,GRP_aileronR};
    static MGROUP_ROTATE Elevator (midx, ElevGrp, 4,
        _V(0,-2.173,-8.84), _V(1,0,0), (float)(30.0*RAD));
    anim_elev = CreateAnimation (0.5);
    AddAnimationComponent (anim_elev, 0, 1, &Elevator);

    // ***** 5. Aileron animation of elevons *****

    static UINT LAileronGrp[2] = {GRP_flapL,GRP_aileronL};
    static MGROUP_ROTATE LAileron (midx, LAileronGrp, 2,
        _V(0,-2.173,-8.84), _V(-1,0,0), (float)(10.0*RAD));
    static UINT RAileronGrp[2] = {GRP_flapR,GRP_aileronR};
    static MGROUP_ROTATE RAileron (midx, RAileronGrp, 2,
        _V(0,-2.173,-8.84), _V(1,0,0), (float)(10.0*RAD));
    anim_laileron = CreateAnimation (0.5);
    AddAnimationComponent (anim_laileron, 0, 1, &LAileron);
    anim_raileron = CreateAnimation (0.5);
    AddAnimationComponent (anim_raileron, 0, 1, &RAileron);

    // ***** 6. Rudder animation *****

    static UINT RudderGrp[2] = {GRP_rudderR,GRP_rudderL};
    static MGROUP_ROTATE Rudder (midx, RudderGrp, 2,
        _V(0,5.77,-12.17), _V(-0.037,0.833,-0.552), (float)(-54.2*RAD));
    anim_rudder = CreateAnimation (0.5);
    AddAnimationComponent (anim_rudder, 0, 1, &Rudder);

    // ***** 7. Speedbrake animation *****

    static UINT SB1Grp[1] = {GRP_rudderR};
    static MGROUP_ROTATE SB1 (midx, SB1Grp, 1,
        _V(0.32,5.77,-12.17), _V(-0.037,0.833,-0.552), (float)(-49.3*RAD));
    static UINT SB2Grp[1] = {GRP_rudderL};
    static MGROUP_ROTATE SB2 (midx, SB2Grp, 1,
        _V(-0.32,5.77,-12.17), _V(0.037,0.833,-0.552), (float)(49.3*RAD));

    anim_spdb = CreateAnimation (0);
    AddAnimationComponent (anim_spdb, 0, 1, &SB1);
    AddAnimationComponent (anim_spdb, 0, 1, &SB2);

    // ***** 8. RMS arm animation *****
    // Note that the animation components can't be declared static here, since
    // their rotation parameters are modified by the respective parent transforms

    ANIMATIONCOMPONENT_HANDLE parent;

    static UINT RMSShoulderYawGrp[1] = {GRP_Shoulder};
    rms_anim[0] = new MGROUP_ROTATE (midx, RMSShoulderYawGrp, 1,
        _V(-2.26, 1.5, 10), _V(0, 1, 0), (float)(-360*RAD)); // -180 .. +180
    anim_arm_sy = CreateAnimation (0.5);
    parent = AddAnimationComponent (anim_arm_sy, 0, 1, rms_anim[0]);

    static UINT RMSShoulderPitchGrp[1] = {GRP_Humerus};
    rms_anim[1] = new MGROUP_ROTATE (midx, RMSShoulderPitchGrp, 1,
        _V(-2.26, 1.8, 10), _V(1, 0, 0), (float)(147*RAD)); // -2 .. +145
    anim_arm_sp = CreateAnimation (0.0136);
    parent = AddAnimationComponent (anim_arm_sp, 0, 1, rms_anim[1], parent);

    static UINT RMSElbowPitchGrp[3] = {GRP_radii,GRP_RMScamera,GRP_RMScamera_pivot};
    rms_anim[2] = new MGROUP_ROTATE (midx, RMSElbowPitchGrp, 3,
        _V(-2.26,1.7,3.3), _V(1,0,0), (float)(-162*RAD)); // -160 .. +2
    anim_arm_ep = CreateAnimation (0.0123);
    parent = AddAnimationComponent (anim_arm_ep, 0, 1, rms_anim[2], parent);

    static UINT RMSWristPitchGrp[1] = {GRP_wrist};
    rms_anim[3] = new MGROUP_ROTATE (midx, RMSWristPitchGrp, 1,
        _V(-2.26,1.7,-3.55), _V(1,0,0), (float)(240*RAD)); // -120 .. +120
    anim_arm_wp = CreateAnimation (0.5);
    parent = AddAnimationComponent (anim_arm_wp, 0, 1, rms_anim[3], parent);

    static UINT RMSWristYawGrp[1] = {GRP_endeffecter};
    rms_anim[4] = new MGROUP_ROTATE (midx, RMSWristYawGrp, 1,
        _V(-2.26,1.7,-4.9), _V(0,1,0), (float)(-240*RAD)); // -120 .. +120
    anim_arm_wy = CreateAnimation (0.5);
    parent = AddAnimationComponent (anim_arm_wy, 0, 1, rms_anim[4], parent);

    rms_anim[5] = new MGROUP_ROTATE (LOCALVERTEXLIST, MAKEGROUPARRAY(arm_tip), 3,
        _V(-2.26,1.7,-6.5), _V(0,0,1), (float)(894*RAD)); // -447 .. +447
    anim_arm_wr = CreateAnimation (0.5);
    hAC_arm = AddAnimationComponent (anim_arm_wr, 0, 1, rms_anim[5], parent);

    // ======================================================
    // VC animation definitions
    // ======================================================
    plop->DefineAnimations (vidx);
}

void Atlantis::AddOrbiterVisual (const VECTOR3 &ofs)
{
    orbiter_ofs = ofs;
    huds.hudcnt = _V(ofs.x-0.671257, ofs.y+2.523535, ofs.z+14.969);

    if (mesh_orbiter == MESH_UNDEFINED) {

        // ***** Load meshes

        mesh_cockpit = AddMesh (hOrbiterCockpitMesh, &ofs);
        SetMeshVisibilityMode (mesh_cockpit, MESHVIS_EXTERNAL);

        mesh_orbiter = AddMesh (hOrbiterMesh, &ofs);
        SetMeshVisibilityMode (mesh_orbiter, MESHVIS_EXTERNAL|MESHVIS_VC|MESHVIS_EXTPASS);

        mesh_vc = AddMesh (hOrbiterVCMesh, &ofs);
        SetMeshVisibilityMode (mesh_vc, MESHVIS_VC);

        for (int i = 0; i < 10; i++) mfds[i].nmesh = mesh_vc;
        huds.nmesh = mesh_vc;

        if (do_cargostatic) {
            VECTOR3 cofs = ofs + cargo_static_ofs;
            AddMesh (cargo_static_mesh_name, &cofs);
        }
        if (do_plat) {
            VECTOR3 plat_ofs = _V(-2.59805, 1.69209, -5.15524);
            AddMesh("shuttle_eva_plat", &plat_ofs);
        }

        // ***** Docking definitions

        SetDockParams (ofs+ORBITER_DOCKPOS, _V(0,1,0), _V(0,0,-1));

        // ***** Attachment definitions

        if (!sat_attach) sat_attach = CreateAttachment (false, ofs+ofs_sts_sat, _V(0,1,0), _V(0,0,1), "X");
        if (!rms_attach) rms_attach = CreateAttachment (false, ofs+arm_tip[0], arm_tip[1]-arm_tip[0], arm_tip[2]-arm_tip[0], "G", true);

        // ***** Cockpit camera definition

        SetCameraOffset (_V(ofs.x-0.67,ofs.y+2.55,ofs.z+14.4));
        oapiVCRegisterHUD (&huds); // register changes in HUD parameters
    }
}

void Atlantis::AddTankVisual (const VECTOR3 &ofs)
{
    if (mesh_tank == MESH_UNDEFINED) {

        // ***** Load mesh
        mesh_tank = AddMesh (hTankMesh, &ofs);
        SetMeshVisibilityMode (mesh_tank, MESHVIS_ALWAYS|MESHVIS_EXTPASS);
    }
}

void Atlantis::AddSRBVisual (int which, const VECTOR3 &ofs)
{
    if (mesh_srb[which] == MESH_UNDEFINED) {

        // ***** Load mesh
        mesh_srb[which] = AddMesh (hSRBMesh, &ofs);
        SetMeshVisibilityMode (mesh_srb[which], MESHVIS_ALWAYS|MESHVIS_EXTPASS);
        int id = AddExhaustRef (EXHAUST_CUSTOM, _V(ofs.x, ofs.y, ofs.z-21.8), 0, 0);
        if (which) srb_id1 = id;
        else       srb_id2 = id;
    }
}

void Atlantis::SeparateBoosters (double met)
{
    // Create SRB's as individual objects
    VESSELSTATUS2 vs;
    VESSELSTATUS2::FUELSPEC fuel;
    VESSELSTATUS2::THRUSTSPEC thrust;
    memset (&vs, 0, sizeof(vs));
    vs.version = 2;
    GetStatusEx (&vs);
    vs.flag = VS_FUELLIST | VS_THRUSTLIST;
    vs.fuel = &fuel;
    vs.nfuel = 1;
    vs.fuel->idx = 0;
    vs.thruster = &thrust;
    vs.nthruster = 1;
    vs.thruster->idx = 0;
    GetSRB_State (met, vs.thruster->level, vs.fuel->level);
    Local2Rel (OFS_LAUNCH_RIGHTSRB, vs.rpos);
    vs.arot.z += 0.25*PI;
    vs.status = 0;
    char name[256];
    strcpy (name, GetName()); strcat (name, "-SRB1");
    oapiCreateVesselEx (name, "Atlantis_SRB", &vs);
    Local2Rel (OFS_LAUNCH_LEFTSRB, vs.rpos);
    vs.arot.z -= 1.5*PI;
    name[strlen(name)-1] = '2';
    oapiCreateVesselEx (name, "Atlantis_SRB", &vs);

    // Remove SRB's from Shuttle instance
    DelPropellantResource (ph_srb);
    DelThrusterGroup (thg_srb, THGROUP_USER, true);

    // remove srb meshes and shift cg
    DelMesh(mesh_srb[1]);
    DelMesh(mesh_srb[0]);
    ShiftCG (OFS_LAUNCH_ORBITER-OFS_WITHTANK_ORBITER);

    // reconfigure
    RecordEvent ("JET", "SRB");
    SetOrbiterTankConfiguration();
}

void Atlantis::SeparateTank (void)
{
    // Create Tank as individual object
    VESSELSTATUS2 vs;
    memset (&vs, 0, sizeof (vs));
    vs.version = 2;
    GetStatusEx (&vs);
    vs.flag = VS_FUELRESET | VS_THRUSTRESET;
    VECTOR3 ofs = OFS_WITHTANK_TANK;
    if (Playback()) // necessary because during playback the CG shift occurs before separation
        ofs -= OFS_WITHTANK_ORBITER;
    VECTOR3 rofs;
    VECTOR3 vel = {0,-1,0};
    static const double dv = 0.3; // tank separation velocity
    Local2Rel (ofs, vs.rpos);
    GlobalRot (vel, rofs);
    vs.rvel += rofs*dv;
    vs.vrot.x = -0.005;
    vs.status = 0;
    char name[256];
    strcpy (name, GetName()); strcat (name, "-Tank");
    oapiCreateVesselEx (name, "Atlantis_Tank", &vs);

    // Remove Tank from shuttle instance
    DelPropellantResource (ph_tank);

    // main engines are done
    DelThrusterGroup (thg_main, THGROUP_MAIN, true);

    // clear launch attitude control system
    DelThrusterGroup (THGROUP_ATT_PITCHUP, true);
    DelThrusterGroup (THGROUP_ATT_PITCHDOWN, true);
    DelThrusterGroup (THGROUP_ATT_BANKLEFT, true);
    DelThrusterGroup (THGROUP_ATT_BANKRIGHT, true);
    DelThrusterGroup (THGROUP_ATT_YAWLEFT, true);
    DelThrusterGroup (THGROUP_ATT_YAWRIGHT, true);

    // remove tank mesh and shift cg
    DelMesh (mesh_tank);
    ShiftCG (OFS_WITHTANK_ORBITER);

    // reconfigure
    RecordEvent ("JET", "ET");
    SetOrbiterConfiguration ();
}

void Atlantis::ToggleGrapple (void)
{
    HWND hDlg;
    OBJHANDLE hV = GetAttachmentStatus (rms_attach);

    if (hV) {  // release satellite

        ATTACHMENTHANDLE hAtt = CanArrest();
        DetachChild (rms_attach);
        if (hDlg = oapiFindDialog (g_Param.hDLL, IDD_RMS)) {
            SetWindowText (GetDlgItem (hDlg, IDC_GRAPPLE), "Grapple");
            EnableWindow (GetDlgItem (hDlg, IDC_STOW), TRUE);
        }
        // check whether the object being ungrappled is ready to be clamped into the payload bay
        if (hAtt) {
            AttachChild (hV, sat_attach, hAtt);
            if (hDlg) {
                SetWindowText (GetDlgItem (hDlg, IDC_PAYLOAD), "Purge");
                EnableWindow (GetDlgItem (hDlg, IDC_PAYLOAD), TRUE);
            }
        }

#ifdef UNDEF
        VECTOR3 pos, dir, rot, gbay, gpos;
        GetAttachmentParams (sat_attach, pos, dir, rot);
        Local2Global (pos, gbay);
        VESSEL *v = oapiGetVesselInterface (hV);
        DWORD nAttach = v->AttachmentCount (true);
        for (DWORD j = 0; j < nAttach; j++) { // now scan all attachment points
            ATTACHMENTHANDLE hAtt = v->GetAttachmentHandle (true, j);
            v->GetAttachmentParams (hAtt, pos, dir, rot);
            v->Local2Global (pos, gpos);
            if (dist (gpos, gbay) < MAX_GRAPPLING_DIST) {
                AttachChild (hV, sat_attach, hAtt);
                return;
            }
        }
#endif

    } else {             // grapple satellite

        VECTOR3 gpos, grms, pos, dir, rot;
        Local2Global (orbiter_ofs+arm_tip[0], grms);  // global position of RMS tip
        
        // Search the complete vessel list for a grappling candidate.
        // Not very scalable ...
        for (DWORD i = 0; i < oapiGetVesselCount(); i++) {
            OBJHANDLE hV = oapiGetVesselByIndex (i);
            if (hV == GetHandle()) continue; // we don't want to grapple ourselves ...
            oapiGetGlobalPos (hV, &gpos);
            if (dist (gpos, grms) < oapiGetSize (hV)) { // in range
                VESSEL *v = oapiGetVesselInterface (hV);
                DWORD nAttach = v->AttachmentCount (true);
                for (DWORD j = 0; j < nAttach; j++) { // now scan all attachment points of the candidate
                    ATTACHMENTHANDLE hAtt = v->GetAttachmentHandle (true, j);
                    const char *id = v->GetAttachmentId (hAtt);
                    if (strncmp (id, "GS", 2)) continue; // attachment point not compatible
                    v->GetAttachmentParams (hAtt, pos, dir, rot);
                    v->Local2Global (pos, gpos);
                    if (dist (gpos, grms) < MAX_GRAPPLING_DIST) { // found one!
                        // check whether satellite is currently clamped into payload bay
                        if (hV == GetAttachmentStatus (sat_attach))
                            DetachChild (sat_attach);
                        AttachChild (hV, rms_attach, hAtt);
                        if (hDlg = oapiFindDialog (g_Param.hDLL, IDD_RMS)) {
                            SetWindowText (GetDlgItem (hDlg, IDC_GRAPPLE), "Release");
                            EnableWindow (GetDlgItem (hDlg, IDC_STOW), FALSE);
                        }
                        return;
                    }
                }
            }
        }

    }
}

void Atlantis::ToggleArrest (void)
{
    HWND hDlg;
    if (SatStowed()) { // purge satellite
        DetachChild (sat_attach, 0.1);
        if (hDlg = oapiFindDialog (g_Param.hDLL, IDD_RMS)) {
            SetWindowText (GetDlgItem (hDlg, IDC_PAYLOAD), "Arrest");
            EnableWindow (GetDlgItem (hDlg, IDC_PAYLOAD), CanArrest() ? TRUE:FALSE);
        }
    } else if (CanArrest()) {           // try to arrest satellite
        ToggleGrapple();
    }
}

// check whether the currently grappled object can be stowed in the cargo bay
ATTACHMENTHANDLE Atlantis::CanArrest (void) const
{
    OBJHANDLE hV = GetAttachmentStatus (rms_attach);
    if (!hV) return 0;
    VESSEL *v = oapiGetVesselInterface (hV);
    DWORD nAttach = v->AttachmentCount (true);
    VECTOR3 pos, dir, rot, gpos, gbay;
    GetAttachmentParams (sat_attach, pos, dir, rot);
    Local2Global (pos, gbay);
    for (DWORD j = 0; j < nAttach; j++) {
        ATTACHMENTHANDLE hAtt = v->GetAttachmentHandle (true, j);
        if (strncmp (v->GetAttachmentId (hAtt), "XS", 2)) continue; // attachment point not compatible
        v->GetAttachmentParams (hAtt, pos, dir, rot);
        v->Local2Global (pos, gpos);
        if (dist (gpos, gbay) < MAX_GRAPPLING_DIST) {
            return hAtt;
        }
    }
    return 0;
}

void Atlantis::SeparateMMU (void)
{
    // Create MMU at docking port
    DOCKHANDLE hDock = GetDockHandle (0);
    if (GetDockStatus(hDock)) return; // something is already attached to this docking port

    int i;
    char name[256];
    OBJHANDLE hVessel;
    for (i = 0; ; i++) {
        sprintf (name, "%s-MMU-%d", GetName(), i+1);
        hVessel = oapiGetVesselByName(name);
        if (!hVessel) break;
    }

    VESSELSTATUS vs;
    GetStatus (vs);
    hMMU = oapiCreateVessel (name, "Nasa_MMU", vs);
    Dock (hMMU, 0, 0, 1);
    oapiSetFocusObject (hMMU);
}

void Atlantis::AutoMainGimbal ()
{
    // Implement automatic gimbal adjustment for main engines to correct for
    // SRB thrust variations. We adjust only the upper engine

    double        F_srb  = SRB_THRUST         *GetThrusterLevel (th_srb[0]);
    double        F_main = ORBITER_MAIN_THRUST*GetThrusterLevel (th_main[0]);
    if (F_main) {
        double        M_srb =   2.51951112*F_srb;  // angular moment from SRB thrust
        double        M_m2  = -12.02495   *F_main; // angular moment from the two lower main engines in neutral position
        double        M_0   = M_srb + M_m2;
        static double ry    =   9.42;  // upper main thruster: y-offset
        static double rz    = -23.295; // upper main thruster: z-offset
        static double ry2   = ry*ry;
        static double rz2   = rz*rz;
        double        F2    = F_main*F_main;
        double        M2    = M_0*M_0;
        double        term  = -M2 + F2*(ry2+rz2);
        double        arg1  = max (0.0, rz2 * term);
        double        scale = 1.0/(F_main*(ry2+rz2));
        double        dz    = (M_0*ry + sqrt (arg1))*scale;
        double          arg2  = max (0.0, ry2 * term);
        double        dy    =-(M_0*rz + sqrt (arg2))*scale;
        if (!arg1 || !arg2) {
            double len = _hypot(dy,dz);
            dy /= len, dz /= len;
        }
        SetThrusterDir (th_main[2], _V(0,dy,dz));
    }
}

void Atlantis::LaunchClamps ()
{
    VECTOR3 F, T, r = _V(0,0,0), Fc = _V(0,0,0), Tc = _V(0,0,0);
    GetThrusterMoment (th_srb[0], F, T);
    Fc.z = -2*F.z;
    Tc.x =  2*T.x;
    GetThrusterMoment (th_main[0], F, T);
    Fc.z -= 2*F.z;
    Fc.y -= 2*F.y;
    Tc.x += 2*T.x;
    GetThrusterMoment (th_main[2], F, T);
    Fc.z -= F.z;
    Fc.y -= F.y;
    Tc.x += T.x;
    r.z = (Fc.y ? Tc.x/Fc.y : 0);
    AddForce (Fc, r);
}

void Atlantis::SetGearParameters (double state)
{
    if (state == 1.0) { // gear fully deployed
        SetTouchdownPoints (_V(0,-4,17), _V(-3.96,-5.6,-4.3), _V(3.96,-5.6,-4.3)); // gear wheel tips
        SetSurfaceFrictionCoeff (0.05, 0.4);
    } else {
        SetTouchdownPoints (_V(0,-2.5,14), _V(-8,-2.8,-9), _V(8,-2.8,-9)); // belly landing
        SetSurfaceFrictionCoeff (0.4, 0.4);
    }
}

void Atlantis::Jettison ()
{
    switch (status) {
    case 0:
    case 3:               // nothing to do
        break;
    case 1:               // abandon boosters
        SeparateBoosters (oapiGetSimTime()-t0); 
        break;
    case 2:               // abandon tank
        SeparateTank();
        break;
    }
}

// Update moving parts of the orbiter's visual: payload bay doors and gear
// This should only be called when the visual exists, e.g. from within
// clbkVisualCreated or clbkAnimate

void Atlantis::UpdateMesh ()
{
    // update animation states
    SetAnimation (anim_gear, gear_proc);
    SetAnimation (anim_spdb, spdb_proc);
    SetAnimation (anim_door, plop->BayDoorStatus.pos);
    SetAnimation (anim_rad,  plop->RadiatorStatus.pos);
    SetAnimation (anim_kubd, plop->KuAntennaStatus.pos);

    SetAnimationArm (anim_arm_sy, arm_sy);
    SetAnimationArm (anim_arm_sp, arm_sp);
    SetAnimationArm (anim_arm_ep, arm_ep);
    SetAnimationArm (anim_arm_wp, arm_wp);
    SetAnimationArm (anim_arm_wy, arm_wy);
    SetAnimationArm (anim_arm_wr, arm_wr);

    // update MFD brightness
    if (vis) {
        int i;
        MESHHANDLE hMesh = GetMesh (vis, mesh_vc);
        for (i = 0; i < 10; i++) {
            MATERIAL *mat = oapiMeshMaterial (hMesh, 10+i);
            mat->emissive.r = mat->emissive.g = mat->emissive.b = (float)mfdbright[i];
        }
    }
}

void Atlantis::ClearMeshes ()
{
    VESSEL::ClearMeshes();
    mesh_orbiter = MESH_UNDEFINED;
    mesh_cockpit = MESH_UNDEFINED;
    mesh_vc      = MESH_UNDEFINED;
    mesh_tank    = MESH_UNDEFINED;
    mesh_srb[0] = mesh_srb[1] = MESH_UNDEFINED;
}

void Atlantis::SetBayDoorPosition (double pos)
{
    SetAnimation (anim_door, pos);
    rdoor_drag = sqrt (min (1.0, pos*3.0));
    ldoor_drag = sqrt (min (1.0, max(0.0, pos-0.3656)*3.0));
}

void Atlantis::SetRadiatorPosition (double pos)
{
    SetAnimation (anim_rad, pos);
}

void Atlantis::SetKuAntennaPosition (double pos)
{
    SetAnimation (anim_kubd, pos);
}

void Atlantis::OperateLandingGear (AnimState::Action action)
{
    if (status < 3) return;
    // operate landing gear only once the orbiter is free from the tank

    gear_status = action;
    RecordEvent ("GEAR", action == AnimState::CLOSING ? "UP" : "DOWN");
}

void Atlantis::RevertLandingGear ()
{
    if (status < 3) return;
    // operate landing gear only once the orbiter is free from the tank

    OperateLandingGear (gear_status == AnimState::CLOSED || gear_status == AnimState::CLOSING ?
        AnimState::OPENING : AnimState::CLOSING);
}

void Atlantis::OperateSpeedbrake (AnimState::Action action)
{
    spdb_status = action;
    RecordEvent ("SPEEDBRAKE", action == AnimState::CLOSING ? "CLOSE" : "OPEN");
}

void Atlantis::RevertSpeedbrake (void)
{
    OperateSpeedbrake (spdb_status == AnimState::CLOSED || spdb_status == AnimState::CLOSING ?
        AnimState::OPENING : AnimState::CLOSING);
}

void Atlantis::SetAnimationArm (UINT anim, double state)
{
    SetAnimation (anim, state);
    arm_moved = true;

    HWND hDlg;
    if (!SatStowed() && (hDlg = oapiFindDialog (g_Param.hDLL, IDD_RMS))) {
        SetWindowText (GetDlgItem (hDlg, IDC_PAYLOAD), "Arrest");
        EnableWindow (GetDlgItem (hDlg, IDC_PAYLOAD), CanArrest() ? TRUE : FALSE);
    }
}

void Atlantis::RedrawPanel_MFDButton (SURFHANDLE surf, int mfd)
{
    HDC hDC = oapiGetDC (surf);

    // D. Beachy: BUGFIX: if MFD powered off, cover separator lines and do not paint buttons
    if (oapiGetMFDMode(mfd) == MFD_NONE) {
        RECT r = { 0,0,255,13 };
        FillRect(hDC, &r, (HBRUSH)GetStockObject(BLACK_BRUSH));
    } else {   // MFD powered on
        HFONT pFont = (HFONT)SelectObject (hDC, g_Param.font[0]);
        SetTextColor (hDC, RGB(0,255,216));
        SetTextAlign (hDC, TA_CENTER);
        SetBkMode (hDC, TRANSPARENT);
        const char *label;
        int x = 24;

        for (int bt = 0; bt < 5; bt++) {
            if (label = oapiMFDButtonLabel (mfd, bt)) {
                TextOut (hDC, x, 1, label, strlen(label));
                x += 42;
            } else break;
        }
        TextOut (hDC, 234, 1, "PG", 2);
        SelectObject (hDC, pFont);
    }

    oapiReleaseDC (surf, hDC);
}

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

// --------------------------------------------------------------
// Set vessel class capabilities from config file
// --------------------------------------------------------------
void Atlantis::clbkSetClassCaps (FILEHANDLE cfg)
{
    if (!oapiReadItem_bool (cfg, "RenderCockpit", render_cockpit))
        render_cockpit = false;
}

// --------------------------------------------------------------
// Set status from a VESSELSTATUS2 structure
// --------------------------------------------------------------
void Atlantis::clbkSetStateEx (const void *status)
{
    // default parameter initialisation
    DefSetStateEx (status);

    // reset vessel-specific parameters to defaults
    //status = 3;
    SetOrbiterConfiguration();
}

// --------------------------------------------------------------
// Read status from scenario file
// --------------------------------------------------------------
void Atlantis::clbkLoadStateEx (FILEHANDLE scn, void *vs)
{
    int action;
    char *line;
    double met = 0.0; // mission elapsed time
    double srbtime = 0.0;
    double sts_sat_x = 0.0;
    double sts_sat_y = 0.0;
    double sts_sat_z = 0.0;
    spdb_status = AnimState::CLOSED; spdb_proc = 0.0;

    while (oapiReadScenario_nextline (scn, line)) {
        if (!_strnicmp (line, "CONFIGURATION", 13)) {
            sscanf (line+13, "%d", &status);
        } else if (!_strnicmp (line, "MET", 3)) {
            sscanf (line+3, "%lf", &met);
        } else if (!_strnicmp (line, "GEAR", 4)) {
            sscanf (line+4, "%d%lf", &action, &gear_proc);
            gear_status = (AnimState::Action)(action+1);
        } else if (!_strnicmp (line, "SPEEDBRAKE", 10)) {
            sscanf (line+10, "%d%lf", &action, &spdb_proc);
            spdb_status = (AnimState::Action)(action+1);
        } else if (!_strnicmp (line, "SRB_IGNITION_TIME", 17)) {
            sscanf (line+17, "%lf", &srbtime);
        } else if (!_strnicmp (line, "SAT_OFS_X", 9)) {
            sscanf (line+9, "%lf", &sts_sat_x);
        } else if (!_strnicmp (line, "SAT_OFS_Y", 9)) {
            sscanf (line+9, "%lf", &sts_sat_y);
        } else if (!_strnicmp (line, "SAT_OFS_Z", 9)) {
            sscanf (line+9, "%lf", &sts_sat_z);
        } else if (!_strnicmp (line, "CARGO_STATIC_MESH", 17)) {
            sscanf (line+17, "%s", cargo_static_mesh_name);
            do_cargostatic = true;
        } else if (!_strnicmp (line, "CARGO_STATIC_OFS", 16)) {
            sscanf (line+16, "%lf%lf%lf", &cargo_static_ofs.x, &cargo_static_ofs.y, &cargo_static_ofs.z);
        } else if (!_strnicmp (line, "ARM_STATUS", 10)) {
            sscanf (line+10, "%lf%lf%lf%lf%lf%lf", &arm_sy, &arm_sp, &arm_ep, &arm_wp, &arm_wy, &arm_wr);
        } else {
            if (plop->ParseScenarioLine (line)) continue; // offer the line to bay door operations
            ParseScenarioLineEx (line, vs);
            // unrecognised option - pass to Orbiter's generic parser
        }
    }
    ofs_sts_sat.x=sts_sat_x;
    ofs_sts_sat.y=sts_sat_y;
    ofs_sts_sat.z=sts_sat_z;

    ClearMeshes();
    switch (status) {
    case 0:
        SetLaunchConfiguration();
        break;
    case 1:
        SetPostLaunchConfiguration (met);
        break;
    case 2:
        SetOrbiterTankConfiguration();
        break;
    case 3:
        SetOrbiterConfiguration();
        break;
    }

    UpdateMesh ();
}

// --------------------------------------------------------------
// Write status to scenario file
// --------------------------------------------------------------
void Atlantis::clbkSaveState (FILEHANDLE scn)
{
    char cbuf[256];

    // default vessel parameters
    VESSEL3::clbkSaveState (scn);

    // custom parameters
    oapiWriteScenario_int (scn, "CONFIGURATION", status);

    if (status == 1)
        oapiWriteScenario_float (scn, "MET", oapiGetSimTime()-t0);

    sprintf (cbuf, "%d %0.4f", gear_status-1, gear_proc);
    oapiWriteScenario_string (scn, "GEAR", cbuf);

    if (spdb_status != AnimState::CLOSED) {
        sprintf (cbuf, "%d %0.4f", spdb_status-1, spdb_proc);
        oapiWriteScenario_string (scn, "SPEEDBRAKE", cbuf);
    }

    sprintf (cbuf, "%0.4f %0.4f %0.4f %0.4f %0.4f %0.4f", arm_sy, arm_sp, arm_ep, arm_wp, arm_wy, arm_wr);
    oapiWriteScenario_string (scn, "ARM_STATUS", cbuf);

    oapiWriteScenario_float (scn, "SAT_OFS_X", ofs_sts_sat.x);
    oapiWriteScenario_float (scn, "SAT_OFS_Y", ofs_sts_sat.y);
    oapiWriteScenario_float (scn, "SAT_OFS_Z", ofs_sts_sat.z);

    if (do_cargostatic) {
        oapiWriteScenario_string (scn, "CARGO_STATIC_MESH", cargo_static_mesh_name);
        if (cargo_static_ofs.x || cargo_static_ofs.y || cargo_static_ofs.z)
            oapiWriteScenario_vec (scn, "CARGO_STATIC_OFS", cargo_static_ofs);
    }

    // save bay door operations status
    plop->SaveState (scn);
}

// --------------------------------------------------------------
// Vessel gains or loses input focus
// --------------------------------------------------------------
void Atlantis::clbkFocusChanged (bool getfocus, OBJHANDLE newv, OBJHANDLE oldv)
{
    if (getfocus) {
        oapiDisableMFDMode (MFD_LANDING);
        // no VTOL MFD mode for Atlantis
    }
}

// --------------------------------------------------------------
// Simulation time step
// --------------------------------------------------------------
void Atlantis::clbkPostStep (double simt, double simdt, double mjd)
{
    double met;
    int i;
    OBJHANDLE hvessel;

    engine_light_level = GetThrusterGroupLevel (THGROUP_MAIN);

    switch (status) {
    case 0: // launch configuration
        if (GetEngineLevel (ENGINE_MAIN) > 0.95) {
            status = 1; // launch
            t0 = simt + SRB_STABILISATION_TIME;   // store designated liftoff time
            RecordEvent ("STATUS", "SRB_IGNITION");
        } else
            AutoMainGimbal();
        break;
    case 1: // SRB's ignited
        met = simt-t0;
        if (met > SRB_SEPARATION_TIME && !Playback() || bManualSeparate) { // separate boosters
            SeparateBoosters (met);
            bManualSeparate = false;
        } else {
            // extract current thrust level and propellant level as a function of time
            double thrust_level, prop_level;
            GetSRB_State (met, thrust_level, prop_level);
            for (i = 0; i < 2; i++)
                SetThrusterLevel (th_srb[i], thrust_level);
            if (met < 0.0) LaunchClamps ();
            AutoMainGimbal();
        }
        break;

    case 2: // post SRB separation
        if (GetPropellantMass (ph_tank) < 1.0 && !Playback() || bManualSeparate) {
            SeparateTank();
            bManualSeparate = false;
        }
        break;
    case 3: // post tank separation
        if (bManualSeparate && GetAttachmentStatus (sat_attach)) {
            DetachChild (sat_attach, 0.1);
            bManualSeparate = false;
        }

        if (do_eva) {
            char name[256];
            strcpy (name, GetName()); strcat (name, "-MMU");
            hvessel = oapiGetVesselByName (name);
            if (!hvessel) {
                SeparateMMU ();
            }
            do_eva = false;
        };
        break;
    }

    // Execute payload bay operations
    plop->Step (simt, simdt);

    // ***** Animate landing gear *****

    if (gear_status >= AnimState::CLOSING) {
        double da = simdt * GEAR_OPERATING_SPEED;
        if (gear_status == AnimState::CLOSING) { // retract gear
            if (gear_proc > 0.0) gear_proc = max (0.0, gear_proc-da);
            else                 gear_status = AnimState::CLOSED;
        } else {                           // deploy gear
            if (gear_proc < 1.0) gear_proc = min (1.0, gear_proc+da);
            else                 gear_status = AnimState::OPEN;
        }
        SetAnimation (anim_gear, gear_proc);
        SetGearParameters (gear_proc);
    }

    // ***** Animate speedbrake *****

    if (spdb_status >= AnimState::CLOSING) {
        double da = simdt * SPEEDBRAKE_OPERATING_SPEED;
        if (spdb_status == AnimState::CLOSING) { // retract brake
            if (spdb_proc > 0.0) spdb_proc = max (0.0, spdb_proc-da);
            else                 spdb_status = AnimState::CLOSED;
        } else {                           // deploy antenna
            if (spdb_proc < 1.0) spdb_proc = min (1.0, spdb_proc+da);
            else                 spdb_status = AnimState::OPEN;
        }
        SetAnimation (anim_spdb, spdb_proc);
    }

    // ***** Stow RMS arm *****

    if (center_arm) {
        double t0 = oapiGetSimTime();
        double dt = t0 - center_arm_t;       // time step
        double da = ARM_OPERATING_SPEED*dt;  // total rotation angle

        // work from the wrist down to the shoulder
        if (da && (arm_wr != 0.5)) {    // zero wrist roll
            if (da >= fabs(arm_wr-0.5)) // finished
                arm_wr = 0.5, da -= fabs(arm_wr-0.5);
            else
                arm_wr -= (arm_wr > 0.5 ? da:-da), da = 0;
            SetAnimationArm (anim_arm_wr, arm_wr);
        }
        if (da && (arm_wy != 0.5)) {    // zero wrist yaw
            if (da >= fabs(arm_wy-0.5)) // finished
                arm_wy = 0.5, da -= fabs(arm_wy-0.5);
            else
                arm_wy -= (arm_wy > 0.5 ? da:-da), da = 0;
            SetAnimationArm (anim_arm_wy, arm_wy);
        }
        if (da && (arm_wp != 0.5)) {    // zero wrist pitch
            if (da >= fabs(arm_wp-0.5)) // finished
                arm_wp = 0.5, da -= fabs(arm_wp-0.5);
            else
                arm_wp -= (arm_wp > 0.5 ? da:-da), da = 0;
            SetAnimationArm (anim_arm_wp, arm_wp);
        }
        if (da && arm_ep) {             // zero elbow pitch
            if (da >= arm_ep)           // finished
                arm_ep = 0.0, da -= arm_ep;
            else
                arm_ep -= da, da = 0;
            SetAnimationArm (anim_arm_ep, arm_ep);
        }
        if (da && (arm_sy != 0.5)) {    // zero shoulder yaw
            if (da >= fabs(arm_sy-0.5)) // finished
                arm_sy = 0.5, da -= fabs(arm_sy-0.5);
            else
                arm_sy -= (arm_sy > 0.5 ? da:-da), da = 0;
            SetAnimationArm (anim_arm_sy, arm_sy);
        }
        if (da && arm_sp) {             // zero shoulder pitch
            if (da >= arm_sp)           // finished
                arm_sp = 0.0, da -= arm_sp;
            else
                arm_sp -= da, da = 0;
            SetAnimationArm (anim_arm_sp, arm_sp);
        }
        center_arm_t = t0;
        if (da) {
            center_arm = false; // finished stowing
            HWND hDlg = oapiFindDialog (g_Param.hDLL, IDD_RMS);
            if (hDlg) EnableWindow (GetDlgItem (hDlg, IDC_GRAPPLE), TRUE);
        }
    }

    if (arm_moved) {
        SetAttachmentParams (rms_attach, orbiter_ofs+arm_tip[0], arm_tip[1]-arm_tip[0], arm_tip[2]-arm_tip[0]);
        arm_moved = false;
    }
}

// --------------------------------------------------------------
// Respond to playback event
// --------------------------------------------------------------
bool Atlantis::clbkPlaybackEvent (double simt, double event_t, const char *event_type, const char *event)
{
    if (!_stricmp (event_type, "JET")) {
        if (!_stricmp (event, "SRB")) {
            bManualSeparate = true;
            return true;
        }
        else if (!_stricmp (event, "ET")) {
            bManualSeparate = true;
            return true;
        }
    } else if (!_stricmp (event_type, "STATUS")) {
        if (!_stricmp (event, "SRB_IGNITION")) {
            status = 1;
            t0 = event_t + SRB_STABILISATION_TIME;
            return true;
        }
    } else if (!_stricmp (event_type, "ADJUST_LAUNCHTIME")) {
        sscanf (event, "%lf", &t0);
        return true;
    } else if (!_stricmp (event_type, "CARGODOOR")) {
        plop->SetDoorAction (!_stricmp (event, "CLOSE") ? AnimState::CLOSING : AnimState::OPENING, true);
        return true;
    } else if (!_stricmp (event_type, "GEAR")) {
        OperateLandingGear (!_stricmp (event, "UP") ? AnimState::CLOSING : AnimState::OPENING);
        return true;
    } else if (!_stricmp (event_type,"SPEEDBRAKE")) {
        OperateSpeedbrake (!_stricmp (event, "CLOSE") ? AnimState::CLOSING : AnimState::OPENING);
        return true;
    } else if (!_stricmp (event_type, "KUBAND")) {
        plop->SetKuAntennaAction (!_stricmp (event, "CLOSE") ? AnimState::CLOSING : AnimState::OPENING);
        return true;
    }

    return false;
}

// --------------------------------------------------------------
// Atlantis mesh loaded
// --------------------------------------------------------------
void Atlantis::clbkVisualCreated (VISHANDLE _vis, int refcount)
{
    if (refcount > 1) return; // we don't support more than one visual per object
    vis = _vis;

#ifdef UNDEF
    // note: orbiter re-applies the animations to the mesh before calling
    // clbkVisualCreated, so updating the mesh here is not necessary

    // reset grappling point
    arm_tip[0] = _V(-2.26,1.71,-6.5);
    arm_tip[1] = _V(-2.26,1.71,-7.5);
    arm_tip[2] = _V(-2.26,2.71,-6.5);

    UpdateMesh ();
#endif
}

// --------------------------------------------------------------
// Atlantis mesh discarded
// --------------------------------------------------------------
void Atlantis::clbkVisualDestroyed (VISHANDLE _vis, int refcount)
{
    if (vis == _vis) vis = NULL;
}

// --------------------------------------------------------------
// Update mesh animation state
// --------------------------------------------------------------
void Atlantis::clbkAnimate (double simt)
{
    UpdateMesh ();
}

// --------------------------------------------------------------
// Respond to MFD mode change
// --------------------------------------------------------------
void Atlantis::clbkMFDMode (int mfd, int mode)
{
    oapiVCTriggerRedrawArea (-1, AID_CDR1_BUTTONS+mfd-MFD_LEFT);
}

// --------------------------------------------------------------
// Load generic glass cockpit mode
// --------------------------------------------------------------
bool Atlantis::clbkLoadGenericCockpit ()
{
    SetCameraOffset (_V(orbiter_ofs.x-0.67,orbiter_ofs.y+2.55,orbiter_ofs.z+14.4));
    SetCameraDefaultDirection (_V(0,0,1));
    return true;
}

// --------------------------------------------------------------
// register VC buttons for the 2 commander MFDs
// (accessible from commander position only)
// --------------------------------------------------------------
void Atlantis::RegisterVC_CdrMFD ()
{
    // activate MFD function buttons
    oapiVCSetAreaClickmode_Quadrilateral (AID_CDR1_BUTTONS, _V(-0.9239,2.0490,15.0595)+orbiter_ofs, _V(-0.7448,2.0490,15.0595)+orbiter_ofs,  _V(-0.9239,2.0280,15.0595)+orbiter_ofs, _V(-0.7448,2.0280,15.0595)+orbiter_ofs);
    oapiVCSetAreaClickmode_Quadrilateral (AID_CDR2_BUTTONS, _V(-0.6546,2.0490,15.0595)+orbiter_ofs, _V(-0.4736,2.0490,15.0595)+orbiter_ofs,  _V(-0.6546,2.0280,15.0595)+orbiter_ofs, _V(-0.4736,2.0280,15.0595)+orbiter_ofs);

    // D. Beachy: register+activate MFD power buttons
    const double powerButtonRadius = 0.0075; // radius of power button on each MFD
    oapiVCRegisterArea (AID_CDR1_PWR, PANEL_REDRAW_NEVER, PANEL_MOUSE_LBDOWN|PANEL_MOUSE_ONREPLAY);
    oapiVCRegisterArea (AID_CDR2_PWR, PANEL_REDRAW_NEVER, PANEL_MOUSE_LBDOWN|PANEL_MOUSE_ONREPLAY);
    oapiVCSetAreaClickmode_Spherical(AID_CDR1_PWR, _V(-0.950, 2.060, 15.060)+orbiter_ofs, powerButtonRadius);  
    oapiVCSetAreaClickmode_Spherical(AID_CDR2_PWR, _V(-0.680, 2.060, 15.060)+orbiter_ofs, powerButtonRadius);  

    // register+activate MFD brightness buttons
    oapiVCRegisterArea (AID_CDR1_BRT, PANEL_REDRAW_NEVER, PANEL_MOUSE_LBDOWN|PANEL_MOUSE_LBPRESSED|PANEL_MOUSE_ONREPLAY);
    oapiVCRegisterArea (AID_CDR2_BRT, PANEL_REDRAW_NEVER, PANEL_MOUSE_LBDOWN|PANEL_MOUSE_LBPRESSED|PANEL_MOUSE_ONREPLAY);
    oapiVCSetAreaClickmode_Quadrilateral (AID_CDR1_BRT, _V(-0.729,2.0675,15.060)+orbiter_ofs, _V(-0.714,2.0675,15.060)+orbiter_ofs, _V(-0.729,2.0525,15.060)+orbiter_ofs, _V(-0.714,2.0525,15.060)+orbiter_ofs);
    oapiVCSetAreaClickmode_Quadrilateral (AID_CDR2_BRT, _V(-0.459,2.0675,15.060)+orbiter_ofs, _V(-0.444,2.0675,15.060)+orbiter_ofs, _V(-0.459,2.0525,15.060)+orbiter_ofs, _V(-0.444,2.0525,15.060)+orbiter_ofs);
}

// --------------------------------------------------------------
// register VC buttons for the 2 pilot MFDs
// (accessible from pilot position only)
// --------------------------------------------------------------
void Atlantis::RegisterVC_PltMFD ()
{
    // activate MFD function buttons
    oapiVCSetAreaClickmode_Quadrilateral (AID_PLT1_BUTTONS, _V(0.4759,2.0490,15.0595)+orbiter_ofs, _V(0.6568,2.0490,15.0595)+orbiter_ofs,  _V(0.4759,2.0280,15.0595)+orbiter_ofs, _V(0.6568,2.0280,15.0595)+orbiter_ofs);
    oapiVCSetAreaClickmode_Quadrilateral (AID_PLT2_BUTTONS, _V(0.7461,2.0490,15.0595)+orbiter_ofs, _V(0.9271,2.0490,15.0595)+orbiter_ofs,  _V(0.7461,2.0280,15.0595)+orbiter_ofs, _V(0.9271,2.0280,15.0595)+orbiter_ofs);

    // D. Beachy: register+activate MFD power buttons
    const double powerButtonRadius = 0.0075; // radius of power button on each MFD
    oapiVCRegisterArea (AID_PLT1_PWR, PANEL_REDRAW_NEVER, PANEL_MOUSE_LBDOWN|PANEL_MOUSE_ONREPLAY);
    oapiVCRegisterArea (AID_PLT2_PWR, PANEL_REDRAW_NEVER, PANEL_MOUSE_LBDOWN|PANEL_MOUSE_ONREPLAY);
    oapiVCSetAreaClickmode_Spherical(AID_PLT1_PWR, _V( 0.450, 2.060, 15.060)+orbiter_ofs, powerButtonRadius);  
    oapiVCSetAreaClickmode_Spherical(AID_PLT2_PWR, _V( 0.720, 2.060, 15.060)+orbiter_ofs, powerButtonRadius);  

    // register+activate MFD brightness buttons
    oapiVCRegisterArea (AID_PLT1_BRT, PANEL_REDRAW_NEVER, PANEL_MOUSE_LBDOWN|PANEL_MOUSE_LBPRESSED|PANEL_MOUSE_ONREPLAY);
    oapiVCRegisterArea (AID_PLT2_BRT, PANEL_REDRAW_NEVER, PANEL_MOUSE_LBDOWN|PANEL_MOUSE_LBPRESSED|PANEL_MOUSE_ONREPLAY);
    oapiVCSetAreaClickmode_Quadrilateral (AID_PLT1_BRT, _V(0.671,2.0675,15.060)+orbiter_ofs, _V(0.686,2.0675,15.060)+orbiter_ofs, _V(0.671,2.0525,15.060)+orbiter_ofs, _V(0.686,2.0525,15.060)+orbiter_ofs);
    oapiVCSetAreaClickmode_Quadrilateral (AID_PLT2_BRT, _V(0.941,2.0675,15.060)+orbiter_ofs, _V(0.956,2.0675,15.060)+orbiter_ofs, _V(0.941,2.0525,15.060)+orbiter_ofs, _V(0.956,2.0525,15.060)+orbiter_ofs);
}

// --------------------------------------------------------------
// register VC buttons for the 5 MFDs on the central panel
// (accessible from commander and pilot positions)
// --------------------------------------------------------------
void Atlantis::RegisterVC_CntMFD ()
{
    // activate MFD function buttons
    oapiVCSetAreaClickmode_Quadrilateral (AID_MFD1_BUTTONS, _V(-0.3579,2.1451,15.0863)+orbiter_ofs, _V(-0.1770,2.1451,15.0863)+orbiter_ofs, _V(-0.3579,2.1241,15.0863)+orbiter_ofs, _V(-0.1770,2.1241,15.0863)+orbiter_ofs);
    oapiVCSetAreaClickmode_Quadrilateral (AID_MFD2_BUTTONS, _V(-0.3579,1.9143,15.0217)+orbiter_ofs, _V(-0.1770,1.9143,15.0217)+orbiter_ofs, _V(-0.3579,1.8933,15.0217)+orbiter_ofs, _V(-0.1770,1.8933,15.0217)+orbiter_ofs);
    oapiVCSetAreaClickmode_Quadrilateral (AID_MFD3_BUTTONS, _V(-0.0888,2.0288,15.0538)+orbiter_ofs, _V(0.0922,2.0288,15.0538)+orbiter_ofs, _V(-0.0888,2.0078,15.0538)+orbiter_ofs, _V(0.0922,2.0078,15.0538)+orbiter_ofs);
    oapiVCSetAreaClickmode_Quadrilateral (AID_MFD4_BUTTONS, _V(0.1795,2.1451,15.0863)+orbiter_ofs, _V(0.3604,2.1451,15.0863)+orbiter_ofs, _V(0.1795,2.1241,15.0863)+orbiter_ofs, _V(0.3604,2.1241,15.0863)+orbiter_ofs);
    oapiVCSetAreaClickmode_Quadrilateral (AID_MFD5_BUTTONS, _V(0.1795,1.9143,15.0217)+orbiter_ofs, _V(0.3604,1.9143,15.0217)+orbiter_ofs, _V(0.1795,1.8933,15.0217)+orbiter_ofs, _V(0.3604,1.8933,15.0217)+orbiter_ofs);

    // D. Beachy: register+activate MFD power buttons
    const double powerButtonRadius = 0.0075; // radius of power button on each MFD
    oapiVCRegisterArea (AID_MFD1_PWR, PANEL_REDRAW_NEVER, PANEL_MOUSE_LBDOWN|PANEL_MOUSE_ONREPLAY);
    oapiVCRegisterArea (AID_MFD2_PWR, PANEL_REDRAW_NEVER, PANEL_MOUSE_LBDOWN|PANEL_MOUSE_ONREPLAY);
    oapiVCRegisterArea (AID_MFD3_PWR, PANEL_REDRAW_NEVER, PANEL_MOUSE_LBDOWN|PANEL_MOUSE_ONREPLAY);
    oapiVCRegisterArea (AID_MFD4_PWR, PANEL_REDRAW_NEVER, PANEL_MOUSE_LBDOWN|PANEL_MOUSE_ONREPLAY);
    oapiVCRegisterArea (AID_MFD5_PWR, PANEL_REDRAW_NEVER, PANEL_MOUSE_LBDOWN|PANEL_MOUSE_ONREPLAY);
    oapiVCSetAreaClickmode_Spherical(AID_MFD1_PWR, _V(-0.383, 2.153, 15.090)+orbiter_ofs, powerButtonRadius);  
    oapiVCSetAreaClickmode_Spherical(AID_MFD2_PWR, _V(-0.383, 1.922, 15.023)+orbiter_ofs, powerButtonRadius);  
    oapiVCSetAreaClickmode_Spherical(AID_MFD3_PWR, _V(-0.114, 2.037, 15.058)+orbiter_ofs, powerButtonRadius);  
    oapiVCSetAreaClickmode_Spherical(AID_MFD4_PWR, _V( 0.155, 2.153, 15.090)+orbiter_ofs, powerButtonRadius);  
    oapiVCSetAreaClickmode_Spherical(AID_MFD5_PWR, _V( 0.155, 1.922, 15.023)+orbiter_ofs, powerButtonRadius);  

    // register+activate MFD brightness buttons
    oapiVCRegisterArea (AID_MFD1_BRT, PANEL_REDRAW_NEVER, PANEL_MOUSE_LBDOWN|PANEL_MOUSE_LBPRESSED|PANEL_MOUSE_ONREPLAY);
    oapiVCRegisterArea (AID_MFD2_BRT, PANEL_REDRAW_NEVER, PANEL_MOUSE_LBDOWN|PANEL_MOUSE_LBPRESSED|PANEL_MOUSE_ONREPLAY);
    oapiVCRegisterArea (AID_MFD3_BRT, PANEL_REDRAW_NEVER, PANEL_MOUSE_LBDOWN|PANEL_MOUSE_LBPRESSED|PANEL_MOUSE_ONREPLAY);
    oapiVCRegisterArea (AID_MFD4_BRT, PANEL_REDRAW_NEVER, PANEL_MOUSE_LBDOWN|PANEL_MOUSE_LBPRESSED|PANEL_MOUSE_ONREPLAY);
    oapiVCRegisterArea (AID_MFD5_BRT, PANEL_REDRAW_NEVER, PANEL_MOUSE_LBDOWN|PANEL_MOUSE_LBPRESSED|PANEL_MOUSE_ONREPLAY);
    oapiVCSetAreaClickmode_Quadrilateral (AID_MFD1_BRT, _V(-0.162,2.1605,15.090)+orbiter_ofs, _V(-0.147,2.1605,15.090)+orbiter_ofs, _V(-0.162,2.1455,15.090)+orbiter_ofs, _V(-0.147,2.1455,15.090)+orbiter_ofs);
    oapiVCSetAreaClickmode_Quadrilateral (AID_MFD2_BRT, _V(-0.162,1.9295,15.023)+orbiter_ofs, _V(-0.147,1.9295,15.023)+orbiter_ofs, _V(-0.162,1.9145,15.023)+orbiter_ofs, _V(-0.147,1.9145,15.023)+orbiter_ofs);
    oapiVCSetAreaClickmode_Quadrilateral (AID_MFD3_BRT, _V(0.107,2.0445,15.058)+orbiter_ofs, _V(0.122,2.0445,15.058)+orbiter_ofs, _V(0.107,2.0295,15.058)+orbiter_ofs, _V(0.122,2.0295,15.058)+orbiter_ofs);
    oapiVCSetAreaClickmode_Quadrilateral (AID_MFD4_BRT, _V(0.376,2.1605,15.090)+orbiter_ofs, _V(0.391,2.1605,15.090)+orbiter_ofs, _V(0.376,2.1455,15.090)+orbiter_ofs, _V(0.391,2.1455,15.090)+orbiter_ofs);
    oapiVCSetAreaClickmode_Quadrilateral (AID_MFD5_BRT, _V(0.376,1.9295,15.023)+orbiter_ofs, _V(0.391,1.9295,15.023)+orbiter_ofs, _V(0.376,1.9145,15.023)+orbiter_ofs, _V(0.391,1.9145,15.023)+orbiter_ofs);
}

// --------------------------------------------------------------
// register VC buttons for the aft MFD at the starbord panel
// (accessible from payload control position only)
// --------------------------------------------------------------
void Atlantis::RegisterVC_AftMFD ()
{
    // register+activate aft MFD function buttons
    SURFHANDLE tex1 = oapiGetTextureHandle (hOrbiterVCMesh, 7);
    oapiVCRegisterArea (AID_MFDA_BUTTONS, _R(0,127,255,140), PANEL_REDRAW_USER, PANEL_MOUSE_LBDOWN|PANEL_MOUSE_LBUP|PANEL_MOUSE_LBPRESSED|PANEL_MOUSE_ONREPLAY, PANEL_MAP_BACKGROUND, tex1);
    oapiVCSetAreaClickmode_Quadrilateral (AID_MFDA_BUTTONS, _V(1.3862,2.2570,13.8686)+orbiter_ofs, _V(1.3862,2.2570,13.6894)+orbiter_ofs, _V(1.3678,2.2452,13.8686)+orbiter_ofs, _V(1.3678,2.2452,13.6894)+orbiter_ofs);

    // register+activate MFD power button
    const double powerButtonRadius = 0.0075; // radius of power button on each MFD
    oapiVCRegisterArea (AID_MFDA_PWR, PANEL_REDRAW_NEVER, PANEL_MOUSE_LBDOWN|PANEL_MOUSE_ONREPLAY);
    oapiVCSetAreaClickmode_Spherical(AID_MFDA_PWR, _V(1.3929,2.2632,13.8947)+orbiter_ofs, powerButtonRadius);

    // register+activate MFD brightness buttons
    oapiVCRegisterArea (AID_MFDA_BRT, PANEL_REDRAW_NEVER, PANEL_MOUSE_LBDOWN|PANEL_MOUSE_LBPRESSED|PANEL_MOUSE_ONREPLAY);
    oapiVCSetAreaClickmode_Quadrilateral (AID_MFDA_BRT, _V(1.4024,2.2675,13.6736)+orbiter_ofs, _V(1.4024,2.2675,13.6586)+orbiter_ofs, _V(1.3893,2.2590,13.6736)+orbiter_ofs, _V(1.3893,2.2590,13.6586)+orbiter_ofs);
}

// --------------------------------------------------------------
// Load virtual cockpit mode
// --------------------------------------------------------------
bool Atlantis::clbkLoadVC (int id)
{
    bool ok = false;

    // register MFD function buttons
    // this needs to be done globally, so that the labels are correctly updated from all VC positions
    SURFHANDLE tex1 = oapiGetTextureHandle (hOrbiterVCMesh, 7);

    // commander MFD function buttons
    oapiVCRegisterArea (AID_CDR1_BUTTONS, _R(0,1,255,14), PANEL_REDRAW_USER, PANEL_MOUSE_LBDOWN|PANEL_MOUSE_LBUP|PANEL_MOUSE_LBPRESSED|PANEL_MOUSE_ONREPLAY, PANEL_MAP_BACKGROUND, tex1);
    oapiVCRegisterArea (AID_CDR2_BUTTONS, _R(0,15,255,28), PANEL_REDRAW_USER, PANEL_MOUSE_LBDOWN|PANEL_MOUSE_LBUP|PANEL_MOUSE_LBPRESSED|PANEL_MOUSE_ONREPLAY, PANEL_MAP_BACKGROUND, tex1);
    // pilot MFD function buttons
    oapiVCRegisterArea (AID_PLT1_BUTTONS, _R(0,29,255,42), PANEL_REDRAW_USER, PANEL_MOUSE_LBDOWN|PANEL_MOUSE_LBUP|PANEL_MOUSE_LBPRESSED|PANEL_MOUSE_ONREPLAY, PANEL_MAP_BACKGROUND, tex1);
    oapiVCRegisterArea (AID_PLT2_BUTTONS, _R(0,43,255,56), PANEL_REDRAW_USER, PANEL_MOUSE_LBDOWN|PANEL_MOUSE_LBUP|PANEL_MOUSE_LBPRESSED|PANEL_MOUSE_ONREPLAY, PANEL_MAP_BACKGROUND, tex1);
    // central console MFD function buttons
    oapiVCRegisterArea (AID_MFD1_BUTTONS, _R(0, 57,255, 70), PANEL_REDRAW_USER, PANEL_MOUSE_LBDOWN|PANEL_MOUSE_LBUP|PANEL_MOUSE_LBPRESSED|PANEL_MOUSE_ONREPLAY, PANEL_MAP_BACKGROUND, tex1);
    oapiVCRegisterArea (AID_MFD2_BUTTONS, _R(0, 71,255, 84), PANEL_REDRAW_USER, PANEL_MOUSE_LBDOWN|PANEL_MOUSE_LBUP|PANEL_MOUSE_LBPRESSED|PANEL_MOUSE_ONREPLAY, PANEL_MAP_BACKGROUND, tex1);
    oapiVCRegisterArea (AID_MFD3_BUTTONS, _R(0, 85,255, 98), PANEL_REDRAW_USER, PANEL_MOUSE_LBDOWN|PANEL_MOUSE_LBUP|PANEL_MOUSE_LBPRESSED|PANEL_MOUSE_ONREPLAY, PANEL_MAP_BACKGROUND, tex1);
    oapiVCRegisterArea (AID_MFD4_BUTTONS, _R(0, 99,255,112), PANEL_REDRAW_USER, PANEL_MOUSE_LBDOWN|PANEL_MOUSE_LBUP|PANEL_MOUSE_LBPRESSED|PANEL_MOUSE_ONREPLAY, PANEL_MAP_BACKGROUND, tex1);
    oapiVCRegisterArea (AID_MFD5_BUTTONS, _R(0,113,255,126), PANEL_REDRAW_USER, PANEL_MOUSE_LBDOWN|PANEL_MOUSE_LBUP|PANEL_MOUSE_LBPRESSED|PANEL_MOUSE_ONREPLAY, PANEL_MAP_BACKGROUND, tex1);

    switch (id) {
    case 0: // commander position
        SetCameraOffset (_V(orbiter_ofs.x-0.67,orbiter_ofs.y+2.55,orbiter_ofs.z+14.4));
        SetCameraDefaultDirection (_V(0,0,1));
        SetCameraMovement (_V(0,0,0.3), 0, 0, _V(-0.3,0,0), 75*RAD, -5*RAD, _V(0.3,0,0), -20*RAD, -27*RAD);
        huds.hudcnt = _V(orbiter_ofs.x-0.671257, orbiter_ofs.y+2.523535, orbiter_ofs.z+14.969);
        oapiVCSetNeighbours (-1, 1, -1, 2);

        RegisterVC_CdrMFD (); // activate commander MFD controls
        RegisterVC_CntMFD (); // activate central panel MFD controls

        ok = true;
        break;
    case 1: // pilot position
        SetCameraOffset (_V(orbiter_ofs.x+0.67,orbiter_ofs.y+2.55,orbiter_ofs.z+14.4));
        SetCameraDefaultDirection (_V(0,0,1));
        SetCameraMovement (_V(0,0,0.3), 0, 0, _V(-0.3,0,0), 20*RAD, -27*RAD, _V(0.3,0,0), -75*RAD, -5*RAD);
        huds.hudcnt = _V(orbiter_ofs.x+0.671257, orbiter_ofs.y+2.523535, orbiter_ofs.z+14.969);
        oapiVCSetNeighbours (0, -1, -1, 2);

        RegisterVC_PltMFD (); // activate pilot MFD controls
        RegisterVC_CntMFD (); // activate central panel MFD controls

        ok = true;
        break;
    case 2: // payload view position
        SetCameraOffset (_V(orbiter_ofs.x+0.4,orbiter_ofs.y+3.15,orbiter_ofs.z+12.8));
        SetCameraDefaultDirection (_V(0,0,-1));
        SetCameraMovement (_V(0,0.20,0.20), 0, 40.0*RAD, _V(0.3,-0.3,0.15), 60.0*RAD, -50.0*RAD, _V(-0.8,0,0), 0, 0);
        oapiVCSetNeighbours (1, 0, -1, 0);

        RegisterVC_AftMFD (); // activate aft MFD controls
        plop->RegisterVC ();  // register panel R13L interface
        ok = true;
        break;
    }

    if (ok) {
        // register the HUDs (synced)
        oapiVCRegisterHUD (&huds);
        // register all MFD displays
        for (int i = 0; i < 10; i++)
            oapiRegisterMFD (MFD_LEFT+i, mfds+i);
        // update panel R13L
        plop->UpdateVC();
    }
    return ok;
}

// --------------------------------------------------------------
// Respond to virtual cockpit mouse event
// --------------------------------------------------------------
bool Atlantis::clbkVCMouseEvent (int id, int event, VECTOR3 &p)
{
    static bool counting = false;
    static double t0 = 0.0;

    switch (id) {
    // handle MFD selection buttons
    case AID_CDR1_BUTTONS:
    case AID_CDR2_BUTTONS:
    case AID_PLT1_BUTTONS:
    case AID_PLT2_BUTTONS:
    case AID_MFD1_BUTTONS:
    case AID_MFD2_BUTTONS:
    case AID_MFD3_BUTTONS:
    case AID_MFD4_BUTTONS: 
    case AID_MFD5_BUTTONS:
    case AID_MFDA_BUTTONS: {
        int mfd = id-AID_CDR1_BUTTONS+MFD_LEFT;
        int bt = (int)(p.x*5.99);
        if (bt < 5) oapiProcessMFDButton (mfd, bt, event);
        else {
            if (event & PANEL_MOUSE_LBDOWN) {
                t0 = oapiGetSysTime();
                counting = true;
            } else if ((event & PANEL_MOUSE_LBUP) && counting) {
                oapiSendMFDKey (mfd, OAPI_KEY_F2);
                counting = false;
            } else if ((event & PANEL_MOUSE_LBPRESSED) && counting && (oapiGetSysTime()-t0 >= 1.0)) {
                oapiSendMFDKey (mfd, OAPI_KEY_F1);
                counting = false;
            }
        }
        } return true;

    // D. Beachy: handle power buttons
    case AID_CDR1_PWR:
    case AID_CDR2_PWR:
    case AID_PLT1_PWR:
    case AID_PLT2_PWR:
    case AID_MFD1_PWR:
    case AID_MFD2_PWR:
    case AID_MFD3_PWR:
    case AID_MFD4_PWR: 
    case AID_MFD5_PWR:
    case AID_MFDA_PWR: {
        int mfd = id - AID_CDR1_PWR+MFD_LEFT;
        oapiSendMFDKey(mfd, OAPI_KEY_ESCAPE);
        } return true;
              
    // handle MFD brightness buttons
    case AID_CDR1_BRT:
    case AID_CDR2_BRT:
    case AID_PLT1_BRT:
    case AID_PLT2_BRT:
    case AID_MFD1_BRT:
    case AID_MFD2_BRT:
    case AID_MFD3_BRT: 
    case AID_MFD4_BRT: 
    case AID_MFD5_BRT:
    case AID_MFDA_BRT: {
        static double t0, brt0;
        static bool up;
        int mfd = id-AID_CDR1_BRT;
        if (event & PANEL_MOUSE_LBDOWN) {
            up = (p.x >= 0.5);
            t0 = oapiGetSysTime();
            brt0 = mfdbright[mfd];
        } else if (event & PANEL_MOUSE_LBPRESSED) {
            double dt = oapiGetSysTime()-t0;
            double brt, dbrt = dt * 0.2;
            if (up) brt = min (1.0, brt0 + dbrt);
            else    brt = max (0.25, brt0 - dbrt);
            mfdbright[mfd] = brt;
            if (vis) {
                MESHHANDLE hMesh = GetMesh (vis, mesh_vc);
                MATERIAL *mat = oapiMeshMaterial (hMesh, 10+mfd);
                mat->emissive.r = mat->emissive.g = mat->emissive.b = (float)brt;
            }
        }
        } return false;

    // handle panel R13L events (payload bay operations)
    case AID_R13L:
        return plop->VCMouseEvent (id, event, p);
    }
    return false;
}

// --------------------------------------------------------------
// Respond to virtual cockpit area redraw request
// --------------------------------------------------------------
bool Atlantis::clbkVCRedrawEvent (int id, int event, SURFHANDLE surf)
{
    switch (id) {
    case AID_CDR1_BUTTONS:
    case AID_CDR2_BUTTONS:
    case AID_PLT1_BUTTONS:
    case AID_PLT2_BUTTONS:
    case AID_MFD1_BUTTONS:
    case AID_MFD2_BUTTONS:
    case AID_MFD3_BUTTONS:
    case AID_MFD4_BUTTONS: 
    case AID_MFD5_BUTTONS:
    case AID_MFDA_BUTTONS: {
        int mfd = id-AID_CDR1_BUTTONS+MFD_LEFT;
        RedrawPanel_MFDButton (surf, mfd);
        } return true;
    default:
        if (id >= AID_R13L_MIN && id <= AID_R13L_MAX)
            return plop->VCRedrawEvent (id, event, surf);
        break;
    }
    return false;
}

// --------------------------------------------------------------
// Respond to a HUD redraw request
// --------------------------------------------------------------
bool Atlantis::clbkDrawHUD (int mode, const HUDPAINTSPEC *hps, oapi::Sketchpad *skp)
{
    // draw the default HUD
    VESSEL3::clbkDrawHUD (mode, hps, skp);
    int cx = hps->CX, cy = hps->CY;

    // show OMS thrust marker
    if (status >= 3) {
        int omsy = cy + (int)(15.0*hps->Scale);
        int dx = (int)(1.0*hps->Scale);
        skp->Line (cx-2*dx, omsy, cx+2*dx, omsy);
        skp->Line (cx, omsy-dx, cx, omsy+dx);
    }

    // show RCS mode
    if (status >= 3 && oapiCockpitMode() == COCKPIT_VIRTUAL) {
        switch (GetAttitudeMode()) {
        case RCS_ROT:
            skp->Text (0, hps->H-13, "RCS ROT", 7);
            break;
        case RCS_LIN:
            skp->Text (0, hps->H-13, "RCS_LIN", 7);
            break;
        }
    }
    return true;
}

// --------------------------------------------------------------
// Keyboard interface handler (buffered key events)
// --------------------------------------------------------------
int Atlantis::clbkConsumeBufferedKey (DWORD key, bool down, char *kstate)
{
    if (!down) return 0; // only process keydown events

    if (KEYMOD_SHIFT (kstate)) {

        switch (key) {
        case OAPI_KEY_E:
            if (status != 3) return 1; // Allow MMU only after orbiter has detached from MT
            return 1;
        }    
    } else if (KEYMOD_CONTROL (kstate)) {
        switch (key) {
        case OAPI_KEY_SPACE: // open RMS control dialog
            oapiOpenDialogEx (g_Param.hDLL, IDD_CTRL, Atlantis_DlgProc, DLG_CAPTIONCLOSE, this);
            return 1;
        case OAPI_KEY_B: // deploy/retract speedbrake
            if (!Playback()) RevertSpeedbrake ();
            return 1;
        case OAPI_KEY_U: // deploy/store Ku-band antenna
            if (!Playback()) plop->RevertKuAntennaAction ();
            return 1;
        }
    } else { // unmodified keys
        switch (key) {
        case OAPI_KEY_G:  // "Landing gear"
            if (!Playback()) RevertLandingGear ();
            return 1;
        case OAPI_KEY_J:  // "Jettison"
            if (!Playback()) bManualSeparate = true;
            return 1;
        case OAPI_KEY_K:  // "Cargo bay doors"
            if (!Playback()) plop->RevertDoorAction ();
            return 1;
        case OAPI_KEY_8:
            ToggleGrapple();
            return 1;
        case OAPI_KEY_9: 
            center_arm = true;
            return 1;
        case OAPI_KEY_E:
            do_eva = true;
            return 1;
        }
    }
    return 0;
}

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

// --------------------------------------------------------------
// Module initialisation
// --------------------------------------------------------------
DLLCLBK void InitModule (HINSTANCE hModule)
{
    g_Param.hDLL = hModule;
    oapiRegisterCustomControls (hModule);
    g_Param.tkbk_label = oapiCreateSurface (LOADBMP (IDB_TKBKLABEL));

    // allocate GDI resources
    g_Param.font[0] = CreateFont (-11, 0, 0, 0, 400, 0, 0, 0, 0, 0, 0, 0, 0, "Arial");
}

DLLCLBK void ExitModule (HINSTANCE hModule)
{
    oapiUnregisterCustomControls (hModule);
    oapiDestroySurface (g_Param.tkbk_label);

    // deallocate GDI resources
    DeleteObject (g_Param.font[0]);
}

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

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

// ==============================================================
// Message callback function for Atlantis control dialog box
// ==============================================================

BOOL CALLBACK Atlantis_DlgProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    Atlantis *sts = (uMsg == WM_INITDIALOG ? (Atlantis*)lParam : (Atlantis*)oapiGetDialogContext (hWnd));
    // pointer to vessel instance was passed as dialog context

    switch (uMsg) {
    case WM_COMMAND:
        switch (LOWORD(wParam)) {
        case IDCANCEL:
            oapiCloseDialog (hWnd);
            return TRUE;
        case IDC_PLBAYOP:
            sts->plop->OpenDialog ();
            break;
        case IDC_RMSOP:
            oapiOpenDialogEx (g_Param.hDLL, IDD_RMS, RMS_DlgProc, DLG_CAPTIONCLOSE, sts);
            break;
        }
        break;
    }
    return oapiDefDialogProc (hWnd, uMsg, wParam, lParam);
}

// ==============================================================
// Message callback function for RMS control dialog box
// ==============================================================

BOOL CALLBACK RMS_DlgProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    Atlantis *sts = (uMsg == WM_INITDIALOG ? (Atlantis*)lParam : (Atlantis*)oapiGetDialogContext (hWnd));
    // pointer to vessel instance was passed as dialog context

    const double step = 0.05*RAD;
    static double t0;
    double t1;
    HICON hIcon;

    switch (uMsg) {
    case WM_INITDIALOG:
        hIcon = LoadIcon (g_Param.hDLL, MAKEINTRESOURCE(IDI_UP));
        SendDlgItemMessage (hWnd, IDC_WRIST_PITCHUP, BM_SETIMAGE, IMAGE_ICON, (LPARAM)hIcon);
        SendDlgItemMessage (hWnd, IDC_ELBOW_PITCHUP, BM_SETIMAGE, IMAGE_ICON, (LPARAM)hIcon);
        SendDlgItemMessage (hWnd, IDC_SHOULDER_PITCHUP, BM_SETIMAGE, IMAGE_ICON, (LPARAM)hIcon);
        hIcon = LoadIcon (g_Param.hDLL, MAKEINTRESOURCE(IDI_DOWN));
        SendDlgItemMessage (hWnd, IDC_WRIST_PITCHDOWN, BM_SETIMAGE, IMAGE_ICON, (LPARAM)hIcon);
        SendDlgItemMessage (hWnd, IDC_ELBOW_PITCHDOWN, BM_SETIMAGE, IMAGE_ICON, (LPARAM)hIcon);
        SendDlgItemMessage (hWnd, IDC_SHOULDER_PITCHDOWN, BM_SETIMAGE, IMAGE_ICON, (LPARAM)hIcon);
        hIcon = LoadIcon (g_Param.hDLL, MAKEINTRESOURCE(IDI_LEFT));
        SendDlgItemMessage (hWnd, IDC_WRIST_YAWLEFT, BM_SETIMAGE, IMAGE_ICON, (LPARAM)hIcon);
        SendDlgItemMessage (hWnd, IDC_SHOULDER_YAWLEFT, BM_SETIMAGE, IMAGE_ICON, (LPARAM)hIcon);
        hIcon = LoadIcon (g_Param.hDLL, MAKEINTRESOURCE(IDI_RIGHT));
        SendDlgItemMessage (hWnd, IDC_WRIST_YAWRIGHT, BM_SETIMAGE, IMAGE_ICON, (LPARAM)hIcon);
        SendDlgItemMessage (hWnd, IDC_SHOULDER_YAWRIGHT, BM_SETIMAGE, IMAGE_ICON, (LPARAM)hIcon);
        hIcon = LoadIcon (g_Param.hDLL, MAKEINTRESOURCE(IDI_RRIGHT));
        SendDlgItemMessage (hWnd, IDC_WRIST_ROLLRIGHT, BM_SETIMAGE, IMAGE_ICON, (LPARAM)hIcon);
        hIcon = LoadIcon (g_Param.hDLL, MAKEINTRESOURCE(IDI_RLEFT));
        SendDlgItemMessage (hWnd, IDC_WRIST_ROLLLEFT, BM_SETIMAGE, IMAGE_ICON, (LPARAM)hIcon);
        SendDlgItemMessage (hWnd, IDC_SHOWGRAPPLE, BM_SETCHECK, oapiGetShowGrapplePoints() ? BST_CHECKED:BST_UNCHECKED, 0);
        SetWindowText (GetDlgItem (hWnd, IDC_GRAPPLE), sts->SatGrappled() ? "Release" : "Grapple");
        EnableWindow (GetDlgItem (hWnd, IDC_STOW), sts->SatGrappled() ? FALSE : TRUE);
        SetWindowText (GetDlgItem (hWnd, IDC_PAYLOAD), sts->SatStowed() ? "Purge" : "Arrest");
        EnableWindow (GetDlgItem (hWnd, IDC_PAYLOAD), sts->SatStowed() || sts->CanArrest() ? TRUE:FALSE);
        SetTimer (hWnd, 1, 50, NULL);
        t0 = oapiGetSimTime();
        return FALSE;
    case WM_DESTROY:
        KillTimer (hWnd, 1);
        return 0;
    case WM_TIMER:
        if (wParam == 1) {
            t1 = oapiGetSimTime();
            if (SendDlgItemMessage (hWnd, IDC_SHOULDER_YAWLEFT, BM_GETSTATE, 0, 0) & BST_PUSHED) {
                sts->arm_sy = min (1.0, sts->arm_sy + (t1-t0)*ARM_OPERATING_SPEED);
                sts->SetAnimationArm (sts->anim_arm_sy, sts->arm_sy);
            } else if (SendDlgItemMessage (hWnd, IDC_SHOULDER_YAWRIGHT, BM_GETSTATE, 0, 0) & BST_PUSHED) {
                sts->arm_sy = max (0.0, sts->arm_sy - (t1-t0)*ARM_OPERATING_SPEED);
                sts->SetAnimationArm (sts->anim_arm_sy, sts->arm_sy);
            } else if (SendDlgItemMessage (hWnd, IDC_SHOULDER_PITCHUP, BM_GETSTATE, 0, 0) & BST_PUSHED) {
                sts->arm_sp = min (1.0, sts->arm_sp + (t1-t0)*ARM_OPERATING_SPEED);
                sts->SetAnimationArm (sts->anim_arm_sp, sts->arm_sp);
            } else if (SendDlgItemMessage (hWnd, IDC_SHOULDER_PITCHDOWN, BM_GETSTATE, 0, 0) & BST_PUSHED) {
                sts->arm_sp = max (0.0, sts->arm_sp - (t1-t0)*ARM_OPERATING_SPEED);
                sts->SetAnimationArm (sts->anim_arm_sp, sts->arm_sp);
            } else if (SendDlgItemMessage (hWnd, IDC_ELBOW_PITCHUP, BM_GETSTATE, 0, 0) & BST_PUSHED) {
                sts->arm_ep = max (0.0, sts->arm_ep - (t1-t0)*ARM_OPERATING_SPEED);
                sts->SetAnimationArm (sts->anim_arm_ep, sts->arm_ep);
            } else if (SendDlgItemMessage (hWnd, IDC_ELBOW_PITCHDOWN, BM_GETSTATE, 0, 0) & BST_PUSHED) {
                sts->arm_ep = min (1.0, sts->arm_ep + (t1-t0)*ARM_OPERATING_SPEED);
                sts->SetAnimationArm (sts->anim_arm_ep, sts->arm_ep);
            } else if (SendDlgItemMessage (hWnd, IDC_WRIST_PITCHUP, BM_GETSTATE, 0, 0) & BST_PUSHED) {
                sts->arm_wp = min (1.0, sts->arm_wp + (t1-t0)*ARM_OPERATING_SPEED);
                sts->SetAnimationArm (sts->anim_arm_wp, sts->arm_wp);
            } else if (SendDlgItemMessage (hWnd, IDC_WRIST_PITCHDOWN, BM_GETSTATE, 0, 0) & BST_PUSHED) {
                sts->arm_wp = max (0.0, sts->arm_wp - (t1-t0)*ARM_OPERATING_SPEED);
                sts->SetAnimationArm (sts->anim_arm_wp, sts->arm_wp);
            } else if (SendDlgItemMessage (hWnd, IDC_WRIST_YAWLEFT, BM_GETSTATE, 0, 0) & BST_PUSHED) {
                sts->arm_wy = min (1.0, sts->arm_wy + (t1-t0)*ARM_OPERATING_SPEED);
                sts->SetAnimationArm (sts->anim_arm_wy, sts->arm_wy);
            } else if (SendDlgItemMessage (hWnd, IDC_WRIST_YAWRIGHT, BM_GETSTATE, 0, 0) & BST_PUSHED) {
                sts->arm_wy = max (0.0, sts->arm_wy - (t1-t0)*ARM_OPERATING_SPEED);
                sts->SetAnimationArm (sts->anim_arm_wy, sts->arm_wy);
            } else if (SendDlgItemMessage (hWnd, IDC_WRIST_ROLLLEFT, BM_GETSTATE, 0, 0) & BST_PUSHED) {
                sts->arm_wr = max (0.0, sts->arm_wr - (t1-t0)*ARM_OPERATING_SPEED);
                sts->SetAnimationArm (sts->anim_arm_wr, sts->arm_wr);
            } else if (SendDlgItemMessage (hWnd, IDC_WRIST_ROLLRIGHT, BM_GETSTATE, 0, 0) & BST_PUSHED) {
                sts->arm_wr = min (1.0, sts->arm_wr + (t1-t0)*ARM_OPERATING_SPEED);
                sts->SetAnimationArm (sts->anim_arm_wr, sts->arm_wr);
            }
            t0 = t1;
        }
        if (!sts->center_arm) EnableWindow (GetDlgItem (hWnd, IDC_GRAPPLE), TRUE);
        break;
    case WM_COMMAND:
        switch (LOWORD(wParam)) {
        case IDCANCEL:
            oapiCloseDialog (hWnd);
            return TRUE;
        case IDC_STOW:
            if (sts->center_arm = !sts->center_arm) {
                sts->center_arm_t = oapiGetSimTime();
                EnableWindow (GetDlgItem (hWnd, IDC_GRAPPLE), FALSE);
            }
            return 0;
        case IDC_GRAPPLE:
            sts->ToggleGrapple();
            return 0;
        case IDC_PAYLOAD:
            sts->ToggleArrest();
            return 0;
        case IDC_SHOWGRAPPLE:
            oapiSetShowGrapplePoints (SendDlgItemMessage (hWnd, IDC_SHOWGRAPPLE, BM_GETCHECK, 0, 0) == BST_CHECKED ? true : false);
            return 0;
        }
        break;
    }
    return oapiDefDialogProc (hWnd, uMsg, wParam, lParam);
}
 

N_Molson

Addon Developer
Addon Developer
Donator
Joined
Mar 5, 2010
Messages
9,296
Reaction score
3,270
Points
203
Location
Toulouse
Yes, the libraries are in the "additional dependancies" field....

and yes for meshres.h, meshres_vc.h, afxres.h and so on...

I don't get it ! :idk:
 

N_Molson

Addon Developer
Addon Developer
Donator
Joined
Mar 5, 2010
Messages
9,296
Reaction score
3,270
Points
203
Location
Toulouse
Yes, that's one of the first things I tried... But it didn't changed anything...

Has someone managed to compile the "Atlantis" solution against Orbiter 2010 P1 ?

VC2008.jpg
 

Izack

Non sequitur
Addon Developer
Joined
Feb 4, 2010
Messages
6,665
Reaction score
13
Points
113
Location
The Wilderness, N.B.
No. I'm getting similar errors to you. I thought it was just my own oversight, though. I've been pulling my hair out trying to find out what I'm missing. :compbash2:
 

Wishbone

Clueless developer
Addon Developer
Joined
Sep 12, 2010
Messages
2,421
Reaction score
1
Points
0
Location
Moscow
Please check your class declarations end in semicolons.
 

N_Molson

Addon Developer
Addon Developer
Donator
Joined
Mar 5, 2010
Messages
9,296
Reaction score
3,270
Points
203
Location
Toulouse
Please check your class declarations end in semicolons.

Since this code has been used to generate the stock Atlantis that works very well, I doubt there can be any error in it ? :hmm:

Let's be clear, I'm not trying to mod it for now, I just want to recompile it (and maybe alter some numerical values like ISP, but nothing more).
 

Wishbone

Clueless developer
Addon Developer
Joined
Sep 12, 2010
Messages
2,421
Reaction score
1
Points
0
Location
Moscow
The sim is unable to find the definition of LightEmitter (line 348 of Atlantis.h).
 

N_Molson

Addon Developer
Addon Developer
Donator
Joined
Mar 5, 2010
Messages
9,296
Reaction score
3,270
Points
203
Location
Toulouse
I solved the LightEmitter problem by including "Orbiter/Orbitersdk/include" in "Project-Properties-C++-General-Other include directories"

That's better, but I still get errors that seem related to anim groups :

Code:
------ Début de la génération*: Projet*: Atlantis, Configuration*: Debug Win32 ------
Compilation en cours...
Atlantis.cpp
.\Atlantis\Atlantis.cpp(608) : error C2065: 'GRP_cargodooroutR'*: identificateur non déclaré
.\Atlantis\Atlantis.cpp(608) : error C2065: 'GRP_cargodoorinR'*: identificateur non déclaré
.\Atlantis\Atlantis.cpp(608) : error C2065: 'GRP_radiatorFR'*: identificateur non déclaré
.\Atlantis\Atlantis.cpp(608) : error C2065: 'GRP_radiatorBR'*: identificateur non déclaré
.\Atlantis\Atlantis.cpp(611) : error C2065: 'GRP_cargodooroutL'*: identificateur non déclaré
.\Atlantis\Atlantis.cpp(611) : error C2065: 'GRP_cargodoorinL'*: identificateur non déclaré
.\Atlantis\Atlantis.cpp(611) : error C2065: 'GRP_radiatorFL'*: identificateur non déclaré
.\Atlantis\Atlantis.cpp(611) : error C2065: 'GRP_radiatorBL'*: identificateur non déclaré
.\Atlantis\Atlantis.cpp(619) : error C2065: 'GRP_radiatorFR'*: identificateur non déclaré
.\Atlantis\Atlantis.cpp(622) : error C2065: 'GRP_radiatorFL'*: identificateur non déclaré
.\Atlantis\Atlantis.cpp(632) : error C2065: 'GRP_nosedoorL'*: identificateur non déclaré
.\Atlantis\Atlantis.cpp(635) : error C2065: 'GRP_nosedoorR'*: identificateur non déclaré
.\Atlantis\Atlantis.cpp(638) : error C2065: 'GRP_nosewheel'*: identificateur non déclaré
.\Atlantis\Atlantis.cpp(638) : error C2065: 'GRP_nosegear'*: identificateur non déclaré
.\Atlantis\Atlantis.cpp(641) : error C2065: 'GRP_geardoorR'*: identificateur non déclaré
.\Atlantis\Atlantis.cpp(644) : error C2065: 'GRP_geardoorL'*: identificateur non déclaré
.\Atlantis\Atlantis.cpp(647) : error C2065: 'GRP_wheelR'*: identificateur non déclaré
.\Atlantis\Atlantis.cpp(647) : error C2065: 'GRP_gearR'*: identificateur non déclaré
.\Atlantis\Atlantis.cpp(647) : error C2065: 'GRP_wheelL'*: identificateur non déclaré
.\Atlantis\Atlantis.cpp(647) : error C2065: 'GRP_gearL'*: identificateur non déclaré
.\Atlantis\Atlantis.cpp(661) : error C2065: 'GRP_startrackers'*: identificateur non déclaré
.\Atlantis\Atlantis.cpp(661) : error C2065: 'GRP_KUband1'*: identificateur non déclaré
.\Atlantis\Atlantis.cpp(661) : error C2065: 'GRP_KUband2'*: identificateur non déclaré
.\Atlantis\Atlantis.cpp(664) : error C2065: 'GRP_KUband2'*: identificateur non déclaré
.\Atlantis\Atlantis.cpp(667) : error C2065: 'GRP_KUband1'*: identificateur non déclaré
.\Atlantis\Atlantis.cpp(667) : error C2065: 'GRP_KUband2'*: identificateur non déclaré
.\Atlantis\Atlantis.cpp(678) : error C2065: 'GRP_flapR'*: identificateur non déclaré
.\Atlantis\Atlantis.cpp(678) : error C2065: 'GRP_flapL'*: identificateur non déclaré
.\Atlantis\Atlantis.cpp(678) : error C2065: 'GRP_aileronL'*: identificateur non déclaré
.\Atlantis\Atlantis.cpp(678) : error C2065: 'GRP_aileronR'*: identificateur non déclaré
.\Atlantis\Atlantis.cpp(686) : error C2065: 'GRP_flapL'*: identificateur non déclaré
.\Atlantis\Atlantis.cpp(686) : error C2065: 'GRP_aileronL'*: identificateur non déclaré
.\Atlantis\Atlantis.cpp(689) : error C2065: 'GRP_flapR'*: identificateur non déclaré
.\Atlantis\Atlantis.cpp(689) : error C2065: 'GRP_aileronR'*: identificateur non déclaré
.\Atlantis\Atlantis.cpp(699) : error C2065: 'GRP_rudderR'*: identificateur non déclaré
.\Atlantis\Atlantis.cpp(699) : error C2065: 'GRP_rudderL'*: identificateur non déclaré
.\Atlantis\Atlantis.cpp(707) : error C2065: 'GRP_rudderR'*: identificateur non déclaré
.\Atlantis\Atlantis.cpp(710) : error C2065: 'GRP_rudderL'*: identificateur non déclaré
.\Atlantis\Atlantis.cpp(724) : error C2065: 'GRP_Shoulder'*: identificateur non déclaré
.\Atlantis\Atlantis.cpp(730) : error C2065: 'GRP_Humerus'*: identificateur non déclaré
.\Atlantis\Atlantis.cpp(736) : error C2065: 'GRP_radii'*: identificateur non déclaré
.\Atlantis\Atlantis.cpp(736) : error C2065: 'GRP_RMScamera'*: identificateur non déclaré
.\Atlantis\Atlantis.cpp(736) : error C2065: 'GRP_RMScamera_pivot'*: identificateur non déclaré
.\Atlantis\Atlantis.cpp(742) : error C2065: 'GRP_wrist'*: identificateur non déclaré
.\Atlantis\Atlantis.cpp(748) : error C2065: 'GRP_endeffecter'*: identificateur non déclaré
Le journal de génération a été enregistré à l'emplacement "file://g:\Orbiter 2010 P1 - Main\Orbitersdk\samples\Atlantis\Atlantis\Debug\BuildLog.htm"
Atlantis - 45 erreur(s), 0 avertissement(s)
========== Génération*: 0 a réussi, 1 a échoué, 0 mis à jour, 0 a été ignoré ==========

I tried to add manually the definitions in Atlantis.h (like UINT GRP_cargodooroutR;), the dll compiles, but then I get a CTD in Orbiter...
 
Last edited:

Xyon

Puts the Fun in Dysfunctional
Administrator
Moderator
Orbiter Contributor
Addon Developer
Webmaster
GFX Staff
Beta Tester
Joined
Aug 9, 2009
Messages
6,929
Reaction score
795
Points
203
Location
10.0.0.1
Website
www.orbiter-radio.co.uk
Preferred Pronouns
she/her
Those look like mesh group index identifiers to me, searching Atlantis.cpp that I have (stock unmodified sample) for one of your "undeclared identifiers" gave this:

Code:
// ***** 2. Landing gear animation *****

	static UINT LNosewheelDoorGrp[1] = {GRP_nosedoorL};
	static MGROUP_ROTATE LNosewheelDoor (midx, LNosewheelDoorGrp, 1,
		_V(-0.78, -2.15, 17), _V(0, 0.195, 0.981), (float)(-60.0*RAD));
	static UINT RNosewheelDoorGrp[1] = {GRP_nosedoorR};
	static MGROUP_ROTATE RNosewheelDoor (midx, RNosewheelDoorGrp, 1,
		_V(0.78, -2.15, 17), _V(0, 0.195, 0.981), (float)(60.0*RAD));
	static UINT NosewheelGrp[2] = {GRP_nosewheel,GRP_nosegear};
	static MGROUP_ROTATE Nosewheel (midx, NosewheelGrp, 2,
		_V(0.0, -2.01, 18.11), _V(1, 0, 0), (float)(94.5*RAD));
	static UINT RGearDoorGrp[1] = {GRP_geardoorR};
	static MGROUP_ROTATE RGearDoor (midx, RGearDoorGrp, 1,
		_V(4.35, -2.64, -1.69), _V(0, 0.02, 0.9), (float)(96.2*RAD));
	static UINT LGearDoorGrp[1] = {GRP_geardoorL};
	static MGROUP_ROTATE LGearDoor (midx, LGearDoorGrp, 1,
		_V(-4.35, -2.64, -1.69), _V(0, 0.02, 0.9), (float)(-96.2*RAD));
	static UINT MainGearGrp[4] = {GRP_wheelR,GRP_gearR,GRP_wheelL,GRP_gearL};
	static MGROUP_ROTATE MainGear (midx, MainGearGrp, 4,
		_V(0, -2.66, -3.68), _V(1, 0, 0), (float)(94.5*RAD));

The others check out too, but I haven't tried compiling the module. Perhaps you could search your own Atlantis.cpp for those undeclared identifiers and see if they appear in your copy?

Edit: It looks like the problem is meshres.h - I would wager those identifiers were declared in that file, which is missing now. I wonder where it went? I found it in the 2010 archive but it is not present in the 2010-P1 archive.
 
Last edited:

N_Molson

Addon Developer
Addon Developer
Donator
Joined
Mar 5, 2010
Messages
9,296
Reaction score
3,270
Points
203
Location
Toulouse
Yep, the part that causes problems is "void Atlantis::DefineAnimations (void)" :

Code:
// --------------------------------------------------------------
// Define animation sequences for moving parts
// --------------------------------------------------------------
void Atlantis::DefineAnimations (void)
{
	UINT midx = 1; // mesh index for all external animations
	UINT vidx = 2; // mesh index for all VC animations

	// ***** 1. Cargo door and radiator animations *****

	static UINT RCargoDoorGrp[4] = {GRP_cargodooroutR,GRP_cargodoorinR,GRP_radiatorFR,GRP_radiatorBR};
	static MGROUP_ROTATE RCargoDoor (midx, RCargoDoorGrp, 4,
		_V(2.88, 1.3, 0), _V(0,0,1), (float)(-175.5*RAD));
	static UINT LCargoDoorGrp[4] = {GRP_cargodooroutL,GRP_cargodoorinL,GRP_radiatorFL,GRP_radiatorBL};
	static MGROUP_ROTATE LCargoDoor (midx, LCargoDoorGrp, 4,
		_V(-2.88, 1.3, 0), _V(0,0,1), (float)(175.5*RAD));

	anim_door = CreateAnimation (0);
	AddAnimationComponent (anim_door, 0.0, 0.4632, &RCargoDoor);
	AddAnimationComponent (anim_door, 0.5368, 1.0, &LCargoDoor);

	static UINT RRadiatorGrp[1] = {GRP_radiatorFR};
	static MGROUP_ROTATE RRadiator (midx, RRadiatorGrp, 1,
		_V(2.88, 1.3, 0), _V(0,0,1), (float)(35.5*RAD));
	static UINT LRadiatorGrp[1] = {GRP_radiatorFL};
	static MGROUP_ROTATE LRadiator (midx, LRadiatorGrp, 1,
		_V(-2.88, 1.3, 0), _V(0,0,1), (float)(-35.5*RAD));

	anim_rad = CreateAnimation (0);
	AddAnimationComponent (anim_rad, 0, 1, &RRadiator);
	AddAnimationComponent (anim_rad, 0, 1, &LRadiator);

	// ***** 2. Landing gear animation *****

	static UINT LNosewheelDoorGrp[1] = {GRP_nosedoorL};
	static MGROUP_ROTATE LNosewheelDoor (midx, LNosewheelDoorGrp, 1,
		_V(-0.78, -2.15, 17), _V(0, 0.195, 0.981), (float)(-60.0*RAD));

So the meshres.h file I have is outdated, and the current version is missing in Orbiter 2010 P1 SDK ? That could explain it.
 

martins

Orbiter Founder
Orbiter Founder
Joined
Mar 31, 2008
Messages
2,448
Reaction score
464
Points
83
Website
orbit.medphys.ucl.ac.uk
meshres.h is not included in the distribution because it should be created automatically during the build process. Your project file should have a rule that creates the meshres.h target from dependency ..\..\..\Meshes\Atlantis\Atlantis.msh by invoking meshc.

Essentially this extracts the mesh labels and puts them as constants into meshres.h. Creating meshres.h as part of the build process should ensure that the group identifiers are always up to date. Similar rules should be present for the low-res and the VC meshes.

Is your project file missing these build rules, or are they not executed properly? What happens when you compile Atlantis.msh?
 

N_Molson

Addon Developer
Addon Developer
Donator
Joined
Mar 5, 2010
Messages
9,296
Reaction score
3,270
Points
203
Location
Toulouse
Ok, I reloaded the original sample from the Orbiter 2010 P1 package to be sure I didn't changed anything.

When I open the project with VC++2008, I'm told that the project must be converted. The process goes smoothly, but I suspect that some information could be lost at this point (especially concerning the project settings).

Notice that I had to point "..\..\lib" in "additionnal dependencies" to get rid of the "LightEmitter problem".

As far as I searched there's no mention to meshc.exe (the utility that comes with Orbiter) in the settings, so the problem is probably there.

Here's the build.log, if it can be of any help... "Journal de génération" = Build Log.

Code:
Journal de génération
 	 	

Générer démarrée : Projet : Atlantis, Configuration : Debug|Win32

Lignes de commande
 	 	

Création du fichier temporaire "g:\Orbiter 2010 P1 - Main\Orbitersdk\samples\Atlantis\Atlantis\Debug\RSP00000333164004.rsp" avec pour contenu
[
/Od /I "g:\Orbiter 2010 P1 - Main\Orbitersdk\samples\Atlantis\Atlantis" /I "..\..\include" /I "c:\source\orbiter\Orbitersdk\include" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_CRT_SECURE_NO_WARNINGS" /D "_WINDLL" /FD /EHsc /RTC1 /MDd /GS- /fp:fast /Fo"g:\Orbiter 2010 P1 - Main\Orbitersdk\samples\Atlantis\Atlantis\Debug\\" /Fd"g:\Orbiter 2010 P1 - Main\Orbitersdk\samples\Atlantis\Atlantis\Debug\vc90.pdb" /W3 /c /Zi /TP ".\Common.cpp"

".\Atlantis\Atlantis.cpp"
]
Création de la ligne de commande "cl.exe @"g:\Orbiter 2010 P1 - Main\Orbitersdk\samples\Atlantis\Atlantis\Debug\RSP00000333164004.rsp" /nologo /errorReport:prompt"
Création du fichier temporaire "g:\Orbiter 2010 P1 - Main\Orbitersdk\samples\Atlantis\Atlantis\Debug\RSP00000433164004.rsp" avec pour contenu
[
/Od /I "g:\Orbiter 2010 P1 - Main\Orbitersdk\samples\Atlantis\Atlantis" /I "..\..\include" /I "c:\source\orbiter\Orbitersdk\include" /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_CRT_SECURE_NO_WARNINGS" /D "_WINDLL" /FD /EHsc /RTC1 /MDd /GS- /fp:fast /Fo"g:\Orbiter 2010 P1 - Main\Orbitersdk\samples\Atlantis\Atlantis\Debug\\" /Fd"g:\Orbiter 2010 P1 - Main\Orbitersdk\samples\Atlantis\Atlantis\Debug\vc90.pdb" /W3 /c /Zi /TP ".\Atlantis\PlBayOp.cpp"
]
Création de la ligne de commande "cl.exe @"g:\Orbiter 2010 P1 - Main\Orbitersdk\samples\Atlantis\Atlantis\Debug\RSP00000433164004.rsp" /nologo /errorReport:prompt"

Fenêtre Sortie
 	 	

Compilation en cours...
Common.cpp
Atlantis.cpp
.\Atlantis\Atlantis.cpp(19) : fatal error C1083: Impossible d'ouvrir le fichier include : 'meshres.h' : No such file or directory
Génération de code en cours...

Résultats
 	 	

Le journal de génération a été enregistré à l'emplacement "file://g:\Orbiter 2010 P1 - Main\Orbitersdk\samples\Atlantis\Atlantis\Debug\BuildLog.htm"
Atlantis - 1 erreur(s), 0 avertissement(s)
 

orb

New member
News Reporter
Joined
Oct 30, 2009
Messages
14,020
Reaction score
4
Points
0
Did you edit the "Orbitersdk/resources/orbiterroot.vsprops" file to change the path to Orbiter?

You don't need to add anything to dependencies or move library files to list of files of the project, or set any of output paths. All you need is to change the value of OrbiterDir user macro in orbiterroot.vsprops file that is in Orbitersdk/resoruces subdirectory of Orbiter (highlighted what has to be changed to path of Orbiter installation of yours):
Code:
<UserMacro
	Name="OrbiterDir"
	Value="[highlight]c:\source\orbiter[/highlight]"
	PerformEnvironmentSet="true"
/>
Using inherited property sheets from resources subdirectory of Orbitersdk is a preferred way of creating projects for Orbiter 2010, and it's also used in some Orbitersdk samples.
 

N_Molson

Addon Developer
Addon Developer
Donator
Joined
Mar 5, 2010
Messages
9,296
Reaction score
3,270
Points
203
Location
Toulouse
Thanks, that solved the dependancies problem, the marcro was indeed set on "c:\source\orbiter" ! :thumbup: It was far from being obvious, though.

Still, meshres.h is causing problems. It's included in the project. When I right click "properties" on it, there's a dialog box that displays "Tool : custom generation tool (in bold characters, meaning it's not the default settings)". I'm pretty sure that the meshc.exe should somehow be "plugged" there.

In the tree, there's a "custom generation step" option, with an empty "command line" file. Should it be mentionned here, and if yes how ?

If feel I'm getting closer ! :hmm:
 

orb

New member
News Reporter
Joined
Oct 30, 2009
Messages
14,020
Reaction score
4
Points
0
The custom step for meshres.h command is:
Code:
echo $(InputPath) - Atlantis\meshres.h | $(SDKDir)\utils\meshc.exe
This may be a case with spaces in path to your Orbiter, so if you have spaces in that path, the meshc.exe can't be executed properly, because file name in a command is delimited with a space. I'm sorry, but I can't check at the moment whether that can be the case, so it's only a speculation.

Either try to move Orbiter to a path where there are no spaces in folder names, or try changing commands for custom step builds to include codes of double quote around that part of the command, like:
Code:
echo $(InputPath) - Atlantis\meshres.h | "$(SDKDir)\utils\meshc.exe"
 

N_Molson

Addon Developer
Addon Developer
Donator
Joined
Mar 5, 2010
Messages
9,296
Reaction score
3,270
Points
203
Location
Toulouse
I tried to install a copy of orbiter 2010 P1 in a simple folder : "G:\Orbiter"

I changed the macro path as pointed above.

And I tried to enter the formula :

Code:
echo $(InputPath) - Atlantis\meshres.h | $(SDKDir)\utils\meshc.exe

...in the "command line" field of meshres.h

Now I'm getting this error :

Code:
Atlantis : warning PRJ0042 : La propriété 'Sorties' de l'étape de génération personnalisée pour le fichier 'meshres.h' n'est pas définie. L'étape de génération personnalisée va être ignorée.

Translation : the 'Outputs' property from the custom generation step for the 'meshres.h' file is not defined. The custom generation step will be ignored.

And of course, after that, I get the "meshres.h not found"...
 

Wishbone

Clueless developer
Addon Developer
Joined
Sep 12, 2010
Messages
2,421
Reaction score
1
Points
0
Location
Moscow
first off, have you tried running meshc from the command line and generating the header without the intermediation of Visual Studio? You have to debug the command line first...
 

N_Molson

Addon Developer
Addon Developer
Donator
Joined
Mar 5, 2010
Messages
9,296
Reaction score
3,270
Points
203
Location
Toulouse
I successfully generated a meshres.h file from atlantis.msh, using the app directly. Maybe it could work if I just link the newly generated meshres.h to the atlantis project.

I'll try that later ! :)

---------- Post added at 10:44 AM ---------- Previous post was at 07:39 AM ----------

I did it ! Thanks everyone ! A small step forward in the understanding of the SDK :lol:

Nota Bene : I had to edit the meshresVC.h file and add manually a "_VC" suffix to all the identifiers.
 
Top