void Airplane::iterate(float dt)
{
+ // The gear might have moved. Change their aerodynamics.
+ updateGearState();
+
_model.iterate();
// FIXME: Consume fuel
return ((GearRec*)_gears.get(g))->gear;
}
-void Airplane::setGearState(bool down, float dt)
+void Airplane::updateGearState()
{
- int i;
- for(i=0; i<_gears.size(); i++) {
+ for(int i=0; i<_gears.size(); i++) {
GearRec* gr = (GearRec*)_gears.get(i);
- if(gr->time == 0) {
- // Non-extensible
- gr->gear->setExtension(1);
- gr->surf->setXDrag(1);
- gr->surf->setYDrag(1);
- gr->surf->setZDrag(1);
- continue;
- }
-
- float diff = dt / gr->time;
- if(!down) diff = -diff;
- float ext = gr->gear->getExtension() + diff;
- if(ext < 0) ext = 0;
- if(ext > 1) ext = 1;
+ float ext = gr->gear->getExtension();
- gr->gear->setExtension(ext);
gr->surf->setXDrag(ext);
gr->surf->setYDrag(ext);
gr->surf->setZDrag(ext);
return _tanks.add(t);
}
-void Airplane::addGear(Gear* gear, float transitionTime)
+void Airplane::addGear(Gear* gear)
{
GearRec* g = new GearRec();
g->gear = gear;
g->surf = 0;
- g->time = transitionTime;
_gears.add(g);
}
// Do this after solveGear, because it creates "gear" objects that
// we don't want to affect.
compileContactPoints();
-
- // Drop the gear (use a really big dt)
- setGearState(true, 1000000);
}
void Airplane::solveGear()
Control* c = (Control*)_cruiseControls.get(i);
_controls.setInput(c->control, c->val);
}
- _controls.applyControls();
+ _controls.applyControls(1000000); // Huge dt value
// The local wind
float wind[3];
Math::mul3(-1, _cruiseState.v, wind);
Math::vmul33(_cruiseState.orient, wind, wind);
- // Gear are up (if they're non-retractable, this is a noop)
- setGearState(false, 100000);
-
// Cruise is by convention at 50% tank capacity
setFuelFraction(0.5);
}
stabilizeThrust();
+ updateGearState();
+
// Precompute thrust in the model, and calculate aerodynamic forces
_model.getBody()->reset();
_model.initIteration();
Control* c = (Control*)_approachControls.get(i);
_controls.setInput(c->control, c->val);
}
- _controls.applyControls();
+ _controls.applyControls(1000000);
// The local wind
float wind[3];
// Approach is by convention at 20% tank capacity
setFuelFraction(0.2);
- // Gear are down
- setGearState(true, 100000);
-
// Run the thrusters until they get to a stable setting. FIXME:
// this is lots of wasted work.
for(i=0; i<_thrusters.size(); i++) {
}
stabilizeThrust();
+ updateGearState();
+
// Precompute thrust in the model, and calculate aerodynamic forces
_model.getBody()->reset();
_model.initIteration();
void addFuselage(float* front, float* back, float width,
float taper=1, float mid=0.5);
int addTank(float* pos, float cap, float fuelDensity);
- void addGear(Gear* g, float transitionTime);
+ void addGear(Gear* g);
void addThruster(Thruster* t, float mass, float* cg);
void addBallast(float* pos, float mass);
int numGear();
Gear* getGear(int g);
- void setGearState(bool down, float dt);
int numTanks();
void setFuelFraction(float frac); // 0-1, total amount of fuel
struct Tank { float pos[3]; float cap; float fill;
float density; int handle; };
struct Fuselage { float front[3], back[3], width, taper, mid; };
- struct GearRec { Gear* gear; Surface* surf; float wgt; float time; };
+ struct GearRec { Gear* gear; Surface* surf; float wgt; };
struct ThrustRec { Thruster* thruster;
int handle; float cg[3]; float mass; };
struct Control { int control; float val; };
void addContactPoint(float* pos);
void compileContactPoints();
float normFactor(float f);
+ void updateGearState();
Model _model;
ControlMap _controls;
out = new OutRec();
out->type = type;
out->object = object;
+ out->oldL = out->oldR = out->time = 0;
_outputs.add(out);
}
map->idx = out->maps.add(map);
// The default ranges differ depending on type!
- map->src1 = map->dst1 = 1;
- map->src0 = map->dst0 = 0;
- if(type==FLAP0 || type==FLAP1 || type==STEER)
- map->src0 = map->dst0 = -1;
- if(type==MAGNETOS)
- map->src1 = map->dst1 = 3;
+ map->src1 = map->dst1 = rangeMax(type);
+ map->src0 = map->dst0 = rangeMin(type);
// And add it to the approproate vectors.
Vector* maps = (Vector*)_inputs.get(input);
for(int i=0; i<_outputs.size(); i++) {
OutRec* o = (OutRec*)_outputs.get(i);
for(int j=0; j<o->maps.size(); j++)
- ((MapRec*)o->maps.get(j))->val = 0;
+ ((MapRec*)(o->maps.get(j)))->val = 0;
}
}
}
}
-void ControlMap::applyControls()
+int ControlMap::getOutputHandle(void* obj, int type)
+{
+ for(int i=0; i<_outputs.size(); i++) {
+ OutRec* o = (OutRec*)_outputs.get(i);
+ if(o->object == obj && o->type == type)
+ return i;
+ }
+}
+
+void ControlMap::setTransitionTime(int handle, float time)
+{
+ ((OutRec*)_outputs.get(handle))->time = time;
+}
+
+float ControlMap::getOutput(int handle)
+{
+ return ((OutRec*)_outputs.get(handle))->oldL;
+}
+
+float ControlMap::getOutputR(int handle)
+{
+ return ((OutRec*)_outputs.get(handle))->oldR;
+}
+
+void ControlMap::applyControls(float dt)
{
int outrec;
for(outrec=0; outrec<_outputs.size(); outrec++) {
rval += val;
}
+ // If there is a finite transition time, clamp the values to
+ // the maximum travel allowed in this dt.
+ if(o->time > 0) {
+ float dl = lval - o->oldL;
+ float dr = rval - o->oldR;
+ float adl = Math::abs(dl);
+ float adr = Math::abs(dr);
+
+ float max = (dt/o->time) * (rangeMax(o->type) - rangeMin(o->type));
+ if(adl > max) dl = dl*max/adl;
+ if(adr > max) dr = dr*max/adr;
+
+ lval = o->oldL + dl;
+ rval = o->oldR + dr;
+ }
+
+ o->oldL = lval;
+ o->oldR = rval;
+
void* obj = o->object;
switch(o->type) {
case THROTTLE: ((Thruster*)obj)->setThrottle(lval); break;
}
}
-}; // namespace yasim
+float ControlMap::rangeMin(int type)
+{
+ // The minimum of the range for each type of control
+ switch(type) {
+ case FLAP0: return -1; // [-1:1]
+ case FLAP1: return -1;
+ case STEER: return -1;
+ case MAGNETOS: return 0; // [0:3]
+ default: return 0; // [0:1]
+ }
+}
+
+float ControlMap::rangeMax(int type)
+{
+ // The maximum of the range for each type of control
+ switch(type) {
+ case FLAP0: return 1; // [-1:1]
+ case FLAP1: return 1;
+ case STEER: return 1;
+ case MAGNETOS: return 3; // [0:3]
+ default: return 1; // [0:1]
+ }
+}
+
+} // namespace yasim
void setInput(int input, float value);
// Calculates and applies the settings received since the last reset().
- void applyControls();
+ void applyControls(float dt);
+
+ // Returns the input/output range appropriate for the given
+ // control. Ailerons go from -1 to 1, while throttles are never
+ // lower than zero, etc...
+ static float rangeMin(int type);
+ static float rangeMax(int type);
+
+ // Each output record is identified by both an object/type tuple
+ // and a numeric handle.
+ int getOutputHandle(void* obj, int type);
+
+ // Sets the transition time for the control output to swing
+ // through its full range.
+ void setTransitionTime(int handle, float time);
+
+ // Retrieves the current value of the control output. Controls
+ // with OPT_SPLIT settable on inputs will have a separately
+ // computed "right side" value.
+ float getOutput(int handle);
+ float getOutputR(int handle);
private:
- struct OutRec { int type; void* object; Vector maps; };
+ struct OutRec { int type; void* object; Vector maps;
+ float oldL, oldR, time; };
struct MapRec { OutRec* out; int idx; int opt; float val;
float src0; float src1; float dst0; float dst1; };
delete[] wr->prop;
delete wr;
}
-
+ for(i=0; i<_controlProps.size(); i++)
+ delete (PropOut*)_controlProps.get(i);
}
void FGFDM::iterate(float dt)
g->setDynamicFriction(attrf(a, "dfric", 0.7));
if(a->hasAttribute("castering"))
g->setCastering(true);
- float transitionTime = attrf(a, "retract-time", 0);
- _airplane.addGear(g, transitionTime);
+ _airplane.addGear(g);
} else if(eq(name, "fuselage")) {
float b[3];
v[0] = attrf(a, "ax");
v[1] = attrf(a, "y");
v[2] = attrf(a, "z");
((Thruster*)_currObj)->setDirection(v);
- } else if(eq(name, "control")) {
+ } else if(eq(name, "control-setting")) {
+ // A cruise or approach control setting
const char* axis = a->getValue("axis");
- if(a->hasAttribute("output")) {
- // assert: output type must match _currObj type!
- const char* output = a->getValue("output");
- int opt = 0;
- opt |= a->hasAttribute("split") ? ControlMap::OPT_SPLIT : 0;
- opt |= a->hasAttribute("invert") ? ControlMap::OPT_INVERT : 0;
- opt |= a->hasAttribute("square") ? ControlMap::OPT_SQUARE : 0;
-
- ControlMap* cm = _airplane.getControlMap();
- if(a->hasAttribute("src0")) {
- cm->addMapping(parseAxis(axis), parseOutput(output),
- _currObj, opt,
- attrf(a, "src0"), attrf(a, "src1"),
- attrf(a, "dst0"), attrf(a, "dst1"));
- } else {
- cm->addMapping(parseAxis(axis), parseOutput(output),
- _currObj, opt);
- }
+ float value = attrf(a, "value", 0);
+ if(_cruiseCurr)
+ _airplane.addCruiseControl(parseAxis(axis), value);
+ else
+ _airplane.addApproachControl(parseAxis(axis), value);
+ } else if(eq(name, "control-input")) {
+
+ // A mapping of input property to a control
+ int axis = parseAxis(a->getValue("axis"));
+ int control = parseOutput(a->getValue("control"));
+ int opt = 0;
+ opt |= a->hasAttribute("split") ? ControlMap::OPT_SPLIT : 0;
+ opt |= a->hasAttribute("invert") ? ControlMap::OPT_INVERT : 0;
+ opt |= a->hasAttribute("square") ? ControlMap::OPT_SQUARE : 0;
+
+ ControlMap* cm = _airplane.getControlMap();
+ if(a->hasAttribute("src0")) {
+ cm->addMapping(axis, control, _currObj, opt,
+ attrf(a, "src0"), attrf(a, "src1"),
+ attrf(a, "dst0"), attrf(a, "dst1"));
} else {
- // assert: must be under a "cruise" or "approach" tag
- float value = attrf(a, "value", 0);
- if(_cruiseCurr)
- _airplane.addCruiseControl(parseAxis(axis), value);
- else
- _airplane.addApproachControl(parseAxis(axis), value);
+ cm->addMapping(axis, control, _currObj, opt);
}
+ } else if(eq(name, "control-output")) {
+ // A property output for a control on the current object
+ ControlMap* cm = _airplane.getControlMap();
+ int type = parseOutput(a->getValue("control"));
+ int handle = cm->getOutputHandle(_currObj, type);
+
+ PropOut* p = new PropOut();
+ p->prop = fgGetNode(a->getValue("prop"), true);
+ p->handle = handle;
+ p->type = type;
+ p->left = !(a->hasAttribute("side") &&
+ eq("right", a->getValue("side")));
+ p->min = attrf(a, "min", cm->rangeMin(type));
+ p->max = attrf(a, "max", cm->rangeMax(type));
+ _controlProps.add(p);
+
+ } else if(eq(name, "control-speed")) {
+ ControlMap* cm = _airplane.getControlMap();
+ int type = parseOutput(a->getValue("control"));
+ int handle = cm->getOutputHandle(_currObj, type);
+ float time = attrf(a, "transition-time", 0);
+
+ cm->setTransitionTime(handle, time);
} else {
*(int*)0=0; // unexpected tag, boom
}
float val = fgGetFloat(a->name, 0);
cm->setInput(a->handle, val);
}
- cm->applyControls();
+ cm->applyControls(dt);
// Weights
for(i=0; i<_weights.size(); i++) {
WeightRec* wr = (WeightRec*)_weights.get(i);
_airplane.setWeight(wr->handle, fgGetFloat(wr->prop));
}
-
- // Gear state
- _airplane.setGearState(fgGetBool("/controls/gear-down"), dt);
}
void FGFDM::setOutputProperties()
{
char buf[256];
int i;
+
+ ControlMap* cm = _airplane.getControlMap();
+ for(i=0; i<_controlProps.size(); i++) {
+ PropOut* p = (PropOut*)_controlProps.get(i);
+ float val = (p->left
+ ? cm->getOutput(p->handle)
+ : cm->getOutputR(p->handle));
+ float rmin = cm->rangeMin(p->type);
+ float rmax = cm->rangeMax(p->type);
+ float frac = (val - rmin) / (rmax - rmin);
+ val = frac*(p->max - p->min) + p->min;
+ p->prop->setFloatValue(val);
+ }
+
float fuelDensity = 718.95; // default to gasoline: ~6 lb/gal
for(i=0; i<_airplane.numTanks(); i++) {
fuelDensity = _airplane.getFuelDensity(i);
if(eq(name, "FLAP1")) return ControlMap::FLAP1;
if(eq(name, "SLAT")) return ControlMap::SLAT;
if(eq(name, "SPOILER")) return ControlMap::SPOILER;
- // error here...
- return *(int*)0;
+ *(int*)0=0;
}
void FGFDM::parseWeight(XMLAttributes* a)
bool FGFDM::eq(const char* a, const char* b)
{
// Figure it out for yourself. :)
- while(*a && *b && *a++ == *b++);
+ while(*a && *b && *a == *b) { a++; b++; }
return !(*a || *b);
}
struct AxisRec { char* name; int handle; };
struct EngRec { char* prefix; Thruster* eng; };
struct WeightRec { char* prop; float size; int handle; };
+ struct PropOut { SGPropertyNode* prop; int handle, type; bool left;
+ float min, max; };
void setOutputProperties();
// Engine types. Contains an EngRec structure.
Vector _thrusters;
+ // Output properties for the ControlMap
+ Vector _controlProps;
+
// Parsing temporaries
void* _currObj;
bool _cruiseCurr;
fgSetFloat("/controls/spoilers", 0);
// Are we at ground level? If so, lift the plane up so the gear
- // clear the ground
+ // clear the ground.
double runway_altitude =
fgGetDouble("/environment/ground-elevation-m") * SG_METER_TO_FEET;
+ fgSetBool("/controls/gear-down", false);
if(get_Altitude() - runway_altitude < 50) {
float minGearZ = 1e18;
for(i=0; i<a->numGear(); i++) {
minGearZ = pos[2];
}
_set_Altitude(runway_altitude - minGearZ*M2FT);
+ fgSetBool("/controls/gear-down", true);
}
// The pilot's eyepoint
SGPropertyNode * node = fgGetNode("gear/gear", i, true);
node->setBoolValue("has-brake", g->getBrake() != 0);
node->setBoolValue("wow", g->getCompressFraction() != 0);
- node->setBoolValue("position", g->getExtension());
}
for(i=0; i<model->numThrusters(); i++) {