Added a property output feature to ControlMap that allows arbitrary binding
and scaling of control values to properties. Also added a time interpolation feature that replaces the hacked-in "retract-time" feature for the gear in a more general way (applicable to flaps, too!). Incompatibly breaks the XML syntax; get new files!
This commit is contained in:
parent
9bae80d94a
commit
ab381e5c01
7 changed files with 185 additions and 81 deletions
|
@ -52,6 +52,9 @@ Airplane::~Airplane()
|
|||
|
||||
void Airplane::iterate(float dt)
|
||||
{
|
||||
// The gear might have moved. Change their aerodynamics.
|
||||
updateGearState();
|
||||
|
||||
_model.iterate();
|
||||
|
||||
// FIXME: Consume fuel
|
||||
|
@ -108,27 +111,12 @@ Gear* Airplane::getGear(int g)
|
|||
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 ext = gr->gear->getExtension();
|
||||
|
||||
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;
|
||||
|
||||
gr->gear->setExtension(ext);
|
||||
gr->surf->setXDrag(ext);
|
||||
gr->surf->setYDrag(ext);
|
||||
gr->surf->setZDrag(ext);
|
||||
|
@ -236,12 +224,11 @@ int Airplane::addTank(float* pos, float cap, float density)
|
|||
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);
|
||||
}
|
||||
|
||||
|
@ -589,9 +576,6 @@ void Airplane::compile()
|
|||
// 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()
|
||||
|
@ -678,16 +662,13 @@ void Airplane::runCruise()
|
|||
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);
|
||||
|
||||
|
@ -700,6 +681,8 @@ void Airplane::runCruise()
|
|||
}
|
||||
stabilizeThrust();
|
||||
|
||||
updateGearState();
|
||||
|
||||
// Precompute thrust in the model, and calculate aerodynamic forces
|
||||
_model.getBody()->reset();
|
||||
_model.initIteration();
|
||||
|
@ -719,7 +702,7 @@ void Airplane::runApproach()
|
|||
Control* c = (Control*)_approachControls.get(i);
|
||||
_controls.setInput(c->control, c->val);
|
||||
}
|
||||
_controls.applyControls();
|
||||
_controls.applyControls(1000000);
|
||||
|
||||
// The local wind
|
||||
float wind[3];
|
||||
|
@ -729,9 +712,6 @@ void Airplane::runApproach()
|
|||
// 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++) {
|
||||
|
@ -741,6 +721,8 @@ void Airplane::runApproach()
|
|||
}
|
||||
stabilizeThrust();
|
||||
|
||||
updateGearState();
|
||||
|
||||
// Precompute thrust in the model, and calculate aerodynamic forces
|
||||
_model.getBody()->reset();
|
||||
_model.initIteration();
|
||||
|
|
|
@ -35,7 +35,7 @@ public:
|
|||
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);
|
||||
|
||||
|
@ -51,7 +51,6 @@ public:
|
|||
|
||||
int numGear();
|
||||
Gear* getGear(int g);
|
||||
void setGearState(bool down, float dt);
|
||||
|
||||
int numTanks();
|
||||
void setFuelFraction(float frac); // 0-1, total amount of fuel
|
||||
|
@ -74,7 +73,7 @@ private:
|
|||
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; };
|
||||
|
@ -94,6 +93,7 @@ private:
|
|||
void addContactPoint(float* pos);
|
||||
void compileContactPoints();
|
||||
float normFactor(float f);
|
||||
void updateGearState();
|
||||
|
||||
Model _model;
|
||||
ControlMap _controls;
|
||||
|
|
|
@ -63,6 +63,7 @@ void ControlMap::addMapping(int input, int type, void* object, int options)
|
|||
out = new OutRec();
|
||||
out->type = type;
|
||||
out->object = object;
|
||||
out->oldL = out->oldR = out->time = 0;
|
||||
_outputs.add(out);
|
||||
}
|
||||
|
||||
|
@ -73,12 +74,8 @@ void ControlMap::addMapping(int input, int type, void* object, int options)
|
|||
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);
|
||||
|
@ -91,7 +88,7 @@ void ControlMap::reset()
|
|||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -114,7 +111,31 @@ void ControlMap::setInput(int input, float val)
|
|||
}
|
||||
}
|
||||
|
||||
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++) {
|
||||
|
@ -139,6 +160,25 @@ void ControlMap::applyControls()
|
|||
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;
|
||||
|
@ -162,4 +202,28 @@ void ControlMap::applyControls()
|
|||
}
|
||||
}
|
||||
|
||||
}; // 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
|
||||
|
|
|
@ -43,10 +43,31 @@ public:
|
|||
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; };
|
||||
|
||||
|
|
|
@ -57,7 +57,8 @@ FGFDM::~FGFDM()
|
|||
delete[] wr->prop;
|
||||
delete wr;
|
||||
}
|
||||
|
||||
for(i=0; i<_controlProps.size(); i++)
|
||||
delete (PropOut*)_controlProps.get(i);
|
||||
}
|
||||
|
||||
void FGFDM::iterate(float dt)
|
||||
|
@ -170,8 +171,7 @@ void FGFDM::startElement(const char* name, const XMLAttributes &atts)
|
|||
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");
|
||||
|
@ -225,34 +225,55 @@ void FGFDM::startElement(const char* name, const XMLAttributes &atts)
|
|||
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;
|
||||
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")) {
|
||||
|
||||
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);
|
||||
}
|
||||
// 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
|
||||
}
|
||||
|
@ -269,22 +290,33 @@ void FGFDM::getExternalInput(float dt)
|
|||
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);
|
||||
|
@ -461,8 +493,7 @@ int FGFDM::parseOutput(const char* name)
|
|||
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)
|
||||
|
@ -484,7 +515,7 @@ 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);
|
||||
}
|
||||
|
||||
|
|
|
@ -30,6 +30,8 @@ private:
|
|||
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();
|
||||
|
||||
|
@ -58,6 +60,9 @@ private:
|
|||
// Engine types. Contains an EngRec structure.
|
||||
Vector _thrusters;
|
||||
|
||||
// Output properties for the ControlMap
|
||||
Vector _controlProps;
|
||||
|
||||
// Parsing temporaries
|
||||
void* _currObj;
|
||||
bool _cruiseCurr;
|
||||
|
|
|
@ -159,9 +159,10 @@ void YASim::init()
|
|||
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++) {
|
||||
|
@ -172,6 +173,7 @@ void YASim::init()
|
|||
minGearZ = pos[2];
|
||||
}
|
||||
_set_Altitude(runway_altitude - minGearZ*M2FT);
|
||||
fgSetBool("/controls/gear-down", true);
|
||||
}
|
||||
|
||||
// The pilot's eyepoint
|
||||
|
@ -418,7 +420,6 @@ void YASim::copyFromYASim()
|
|||
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++) {
|
||||
|
|
Loading…
Add table
Reference in a new issue