#include "Airplane.hpp"
namespace yasim {
-// FIXME: hook gear extension into the force calculation somehow...
-
Airplane::Airplane()
{
_emptyWeight = 0;
_wing = 0;
_tail = 0;
_ballast = 0;
- _cruiseRho = 0;
+ _cruiseP = 0;
+ _cruiseT = 0;
_cruiseSpeed = 0;
_cruiseWeight = 0;
- _approachRho = 0;
+ _approachP = 0;
+ _approachT = 0;
_approachSpeed = 0;
_approachAoA = 0;
_approachWeight = 0;
{
_model.iterate();
- // Consume fuel
- // FIXME
+ // FIXME: Consume fuel
}
ControlMap* Airplane::getControlMap()
void Airplane::setApproach(float speed, float altitude, float aoa)
{
_approachSpeed = speed;
- _approachRho = Atmosphere::getStdDensity(altitude);
+ _approachP = Atmosphere::getStdPressure(altitude);
+ _approachT = Atmosphere::getStdTemperature(altitude);
_approachAoA = aoa;
}
void Airplane::setCruise(float speed, float altitude)
{
_cruiseSpeed = speed;
- _cruiseRho = Atmosphere::getStdDensity(altitude);
+ _cruiseP = Atmosphere::getStdPressure(altitude);
+ _cruiseT = Atmosphere::getStdTemperature(altitude);
_cruiseAoA = 0;
_tailIncidence = 0;
}
wr->handle = _model.getBody()->addMass(0, pos);
wr->surf = new Surface();
+ wr->surf->setPosition(pos);
wr->surf->setTotalDrag(size*size);
_model.addSurface(wr->surf);
_surfs.add(wr->surf);
float len = Math::mag3(fwd);
float wid = f->width;
int segs = (int)Math::ceil(len/wid);
- float segWgt = wid*wid/segs;
+ float segWgt = len*wid/segs;
for(int j=0; j<segs; j++) {
float frac = (j+0.5) / segs;
float pos[3];
// Put the surface at the half-way point on the gear strut, give
// it a drag coefficient equal to a square of the same dimension
- // (gear are really draggy) and make it symmetric.
+ // (gear are really draggy) and make it symmetric. Assume that
+ // the "length" of the gear is 3x the compression distance
float pos[3], cmp[3];
g->getCompression(cmp);
- float length = Math::mag3(cmp);
+ float length = 3 * Math::mag3(cmp);
g->getPosition(pos);
Math::mul3(0.5, cmp, cmp);
Math::add3(pos, cmp, pos);
tr->handle = _model.addThruster(tr->thruster);
}
+ // Ground effect
+ float gepos[3];
+ float gespan = _wing->getGroundEffect(gepos);
+ _model.setGroundEffect(gepos, gespan, .3);
+
solveGear();
solve();
void Airplane::stabilizeThrust()
{
- float thrust[3], tmp[3];
- float last = 0;
- while(1) {
- thrust[0] = thrust[1] = thrust[2] = 0;
- for(int i=0; i<_thrusters.size(); i++) {
- Thruster* t = _model.getThruster(i);
- t->integrate(0.033);
- t->getThrust(tmp);
- Math::add3(thrust, tmp, thrust);
- }
-
- float t = Math::mag3(thrust);
- float ratio = (t+0.1)/(last+0.1);
- if(ratio < 1.00001 && ratio > 0.99999)
- break;
-
- last = t;
- }
+ for(int i=0; i<_thrusters.size(); i++)
+ _model.getThruster(i)->stabilize();
}
void Airplane::runCruise()
{
setupState(_cruiseAoA, _cruiseSpeed, &_cruiseState);
_model.setState(&_cruiseState);
- _model.setAirDensity(_cruiseRho);
+ _model.setAir(_cruiseP, _cruiseT);
// The control configuration
_controls.reset();
for(int i=0; i<_thrusters.size(); i++) {
Thruster* t = ((ThrustRec*)_thrusters.get(i))->thruster;
t->setWind(wind);
- t->setDensity(_cruiseRho);
+ t->setAir(_cruiseP, _cruiseT);
}
stabilizeThrust();
{
setupState(_approachAoA, _approachSpeed, &_approachState);
_model.setState(&_approachState);
- _model.setAirDensity(_approachRho);
+ _model.setAir(_approachP, _approachT);
// The control configuration
_controls.reset();
for(int i=0; i<_thrusters.size(); i++) {
Thruster* t = ((ThrustRec*)_thrusters.get(i))->thruster;
t->setWind(wind);
- t->setDensity(_approachRho);
+ t->setAir(_approachP, _approachT);
}
stabilizeThrust();
Vector _cruiseControls;
State _cruiseState;
- float _cruiseRho;
+ float _cruiseP;
+ float _cruiseT;
float _cruiseSpeed;
float _cruiseWeight;
Vector _approachControls;
State _approachState;
- float _approachRho;
+ float _approachP;
+ float _approachT;
float _approachSpeed;
float _approachAoA;
float _approachWeight;
// McCormick lists 299.16/101325/1.22500, but those don't agree with
// R=287. I chose to correct the temperature to 288.20, since 79F is
// pretty hot for a "standard" atmosphere.
-// meters kelvin kPa kg/m^3
+// meters kelvin Pa kg/m^3
float Atmosphere::data[][4] = {{ 0, 288.20, 101325, 1.22500 },
{ 900, 282.31, 90971, 1.12260 },
{ 1800, 276.46, 81494, 1.02690 },
#include "Jet.hpp"
#include "Thruster.hpp"
+#include "PropEngine.hpp"
#include "Gear.hpp"
#include "Wing.hpp"
#include "Math.hpp"
switch(o->type) {
case THROTTLE: ((Thruster*)obj)->setThrottle(lval); break;
case MIXTURE: ((Thruster*)obj)->setMixture(lval); break;
- case PROP: ((Thruster*)obj)->setPropAdvance(lval); break;
+ case ADVANCE: ((PropEngine*)obj)->setAdvance(lval); break;
case REHEAT: ((Jet*)obj)->setReheat(lval); break;
case BRAKE: ((Gear*)obj)->setBrake(lval); break;
case STEER: ((Gear*)obj)->setRotation(lval); break;
public:
~ControlMap();
- enum OutputType { THROTTLE, MIXTURE, REHEAT, PROP,
+ enum OutputType { THROTTLE, MIXTURE, ADVANCE, REHEAT, PROP,
BRAKE, STEER, EXTEND,
INCIDENCE, FLAP0, FLAP1, SLAT, SPOILER };
static const float LBS2KG = 0.45359237;
static const float CM2GALS = 264.172037284;
static const float HP2W = 745.700;
+static const float INHG2PA = 3386.389;
// Stubs, so that this can be compiled without the FlightGear
// binary. What's the best way to handle this?
v[2] = attrf(a, "z");
float mass = attrf(a, "mass") * LBS2KG;
j->setDryThrust(attrf(a, "thrust") * LBS2N);
+ j->setReheatThrust(attrf(a, "afterburner", 0) * LBS2N);
j->setPosition(v);
_airplane.addThruster(j, mass, v);
sprintf(buf, "/engines/engine[%d]", _nextEngine++);
float radius = attrf(a, "radius");
float speed = attrf(a, "cruise-speed") * KTS2MPS;
float omega = attrf(a, "cruise-rpm") * RPM2RAD;
- float rho = Atmosphere::getStdDensity(attrf(a, "alt") * FT2M);
- float power = attrf(a, "takeoff-power") * HP2W;
- float omega0 = attrf(a, "takeoff-rpm") * RPM2RAD;
-
- // FIXME: this is a hack. Find a better way to ask the engine how
- // much power it can produce under cruise conditions.
- float cruisePower = (power * (rho/Atmosphere::getStdDensity(0))
- * (omega/omega0));
-
- Propeller* prop = new Propeller(radius, speed, omega, rho, cruisePower,
- omega0, power);
- PistonEngine* eng = new PistonEngine(power, omega0);
+ float power = attrf(a, "cruise-power") * HP2W;
+ float rho = Atmosphere::getStdDensity(attrf(a, "cruise-alt") * FT2M);
+
+ // Hack, fix this pronto:
+ float engP = attrf(a, "eng-power") * HP2W;
+ float engS = attrf(a, "eng-rpm") * RPM2RAD;
+
+ Propeller* prop = new Propeller(radius, speed, omega, rho, power);
+ PistonEngine* eng = new PistonEngine(engP, engS);
PropEngine* thruster = new PropEngine(prop, eng, moment);
_airplane.addThruster(thruster, mass, cg);
+ if(a->hasAttribute("turbo-mul")) {
+ float mul = attrf(a, "turbo-mul");
+ float mp = attrf(a, "wastegate-mp", 1e6) * INHG2PA;
+ eng->setTurboParams(mul, mp);
+ }
+
+ if(a->hasAttribute("takeoff-power")) {
+ float power0 = attrf(a, "takeoff-power") * HP2W;
+ float omega0 = attrf(a, "takeoff-rpm") * RPM2RAD;
+ prop->setTakeoff(omega0, power0);
+ }
+
+ if(a->hasAttribute("max-rpm")) {
+ float max = attrf(a, "max-rpm") * RPM2RAD;
+ float min = attrf(a, "min-rpm") * RPM2RAD;
+ thruster->setVariableProp(min, max);
+ }
+
char buf[64];
sprintf(buf, "/engines/engine[%d]", _nextEngine++);
EngRec* er = new EngRec();
{
if(eq(name, "THROTTLE")) return ControlMap::THROTTLE;
if(eq(name, "MIXTURE")) return ControlMap::MIXTURE;
+ if(eq(name, "ADVANCE")) return ControlMap::ADVANCE;
if(eq(name, "REHEAT")) return ControlMap::REHEAT;
if(eq(name, "PROP")) return ControlMap::PROP;
if(eq(name, "BRAKE")) return ControlMap::BRAKE;
void Gear::setBrake(float brake)
{
- _brake = brake;
+ _brake = Math::clamp(brake, 0, 1);
}
void Gear::setRotation(float rotation)
void Gear::setExtension(float extension)
{
- _extension = extension;
+ _extension = Math::clamp(extension, 0, 1);
}
void Gear::getPosition(float* out)
{
_rho0 = Atmosphere::getStdDensity(0);
_thrust = 0;
+ _abThrust = 0;
_reheat = 0;
}
-Thruster* Jet::clone()
+void Jet::stabilize()
{
- Jet* j = new Jet();
- j->_thrust = _thrust;
- j->_rho0 = _rho0;
- return j;
+ return; // no-op for now
}
void Jet::setDryThrust(float thrust)
_thrust = thrust;
}
+void Jet::setReheatThrust(float thrust)
+{
+ _abThrust = thrust;
+}
+
void Jet::setReheat(float reheat)
{
- _reheat = reheat;
+ _reheat = Math::clamp(reheat, 0, 1);
}
void Jet::getThrust(float* out)
{
- float t = _thrust * _throttle * (_rho / _rho0);
+ float t = _thrust * _throttle;
+ t += (_abThrust - _thrust) * _reheat;
+ t *= _rho / _rho0;
Math::mul3(t, _dir, out);
}
public:
Jet();
- virtual Thruster* clone();
+ virtual Jet* getJet() { return this; }
void setDryThrust(float thrust);
+ void setReheatThrust(float thrust);
void setReheat(float reheat);
virtual void getThrust(float* out);
virtual void getGyro(float* out);
virtual float getFuelFlow();
virtual void integrate(float dt);
+ virtual void stabilize();
private:
float _thrust;
+ float _abThrust;
float _rho0;
float _reheat;
};
#include "Math.hpp"
namespace yasim {
+float Math::clamp(float val, float min, float max)
+{
+ if(val < min) return min;
+ if(val > max) return max;
+ return val;
+}
+
float Math::abs(float f)
{
return ::fabs(f);
class Math
{
public:
+ // Dumb utilities
+ static float clamp(float val, float min, float max);
+
// Simple wrappers around library routines
static float abs(float f);
static float sqrt(float f);
localWind(pos, _s, v);
t->setWind(v);
- t->setDensity(_rho);
+ t->setAir(_P, _T);
t->integrate(_integrator.getInterval());
t->getTorque(v);
return _thrusters.add(t);
}
+int Model::numThrusters()
+{
+ return _thrusters.size();
+}
+
Thruster* Model::getThruster(int handle)
{
return (Thruster*)_thrusters.get(handle);
return _gears.add(gear);
}
+void Model::setGroundEffect(float* pos, float span, float mul)
+{
+ Math::set3(pos, _wingCenter);
+ _groundEffectSpan = span;
+ _groundEffect = mul;
+}
+
// The first three elements are a unit vector pointing from the global
// origin to the plane, the final element is the distance from the
// origin (the radius of the earth, in most implementations). So
_ground[3] = fromOrigin;
}
-void Model::setAirDensity(float rho)
-{
- _rho = rho;
-}
-
void Model::setAir(float pressure, float temp)
{
+ _P = pressure;
+ _T = temp;
_rho = Atmosphere::calcDensity(pressure, temp);
}
// Do each surface, remembering that the local velocity at each
// point is different due to rotation.
+ float faero[3];
+ faero[0] = faero[1] = faero[2] = 0;
for(int i=0; i<_surfaces.size(); i++) {
Surface* sf = (Surface*)_surfaces.get(i);
float force[3], torque[3];
sf->calcForce(vs, _rho, force, torque);
+ Math::add3(faero, force, faero);
_body.addForce(pos, force);
_body.addTorque(torque);
}
float ground[4];
ground[3] = localGround(s, ground);
+ // Account for ground effect by multiplying the vertical force
+ // component by an amount linear with the fraction of the wingspan
+ // above the ground.
+ float dist = ground[4] - Math::dot3(ground, _wingCenter);
+ if(dist > 0 && dist < _groundEffectSpan) {
+ float fz = Math::dot3(faero, ground);
+ Math::mul3(fz * _groundEffect * dist/_groundEffectSpan,
+ ground, faero);
+ _body.addForce(faero);
+ }
+
// Convert the velocity and rotation vectors to local coordinates
float lrot[3], lv[3];
Math::vmul33(s->orient, s->rot, lrot);
Gear* getGear(int handle);
// Semi-private methods for use by the Airplane solver.
+ int numThrusters();
Thruster* getThruster(int handle);
void setThruster(int handle, Thruster* t);
void initIteration();
// Per-iteration settables
//
void setGroundPlane(double* planeNormal, double fromOrigin);
+ void setGroundEffect(float* pos, float span, float mul);
void setWind(float* wind);
- void setAirDensity(float rho);
void setAir(float pressure, float temp);
// BodyEnvironment callbacks
Vector _surfaces;
Vector _gears;
+ float _groundEffectSpan;
+ float _groundEffect;
+ float _wingCenter[3];
+
double _ground[4];
+ float _P;
+ float _T;
float _rho;
float _wind[3];
+#include "Atmosphere.hpp"
+#include "Math.hpp"
#include "PistonEngine.hpp"
namespace yasim {
_omega0 = speed;
// We must be at sea level under standard conditions
- _rho0 = 1.225; // kg/m^3
+ _rho0 = Atmosphere::getStdDensity(0);
// Further presume that takeoff is (duh) full throttle and
// peak-power, that means that by our efficiency function, we are
// at 11/8 of "ideal" fuel flow.
float realFlow = _f0 * (11.0/8.0);
_mixCoeff = realFlow * 1.1 / _omega0;
+
+ _turbo = 1;
+ _maxMP = 1e6; // No waste gate on non-turbo engines.
+}
+
+void PistonEngine::setTurboParams(float turbo, float maxMP)
+{
+ _turbo = turbo;
+ _maxMP = maxMP;
+
+ // This changes the "sea level" manifold air density
+ float P0 = Atmosphere::getStdPressure(0);
+ float P = P0 * _turbo;
+ if(P > _maxMP) P = _maxMP;
+ float T = Atmosphere::getStdTemperature(0) * Math::pow(P/P0, 2./7.);
+ _rho0 = P / (287.1 * T);
+}
+
+float PistonEngine::getPower()
+{
+ return _P0;
}
void PistonEngine::setThrottle(float t)
_mixture = m;
}
-void PistonEngine::calc(float density, float speed,
+void PistonEngine::calc(float P, float T, float speed,
float* torqueOut, float* fuelFlowOut)
{
// The actual fuel flow
float fuel = _mixture * _mixCoeff * speed;
// manifold air density
+ if(_turbo != 1) {
+ float P1 = P * _turbo;
+ if(P1 > _maxMP) P1 = _maxMP;
+ T *= Math::pow(P1/P, 2./7.);
+ P = P1;
+ }
+ float density = P / (287.1 * T);
+
float rho = density * _throttle;
// How much fuel could be burned with ideal (i.e. uncorrected!)
public:
// Initializes an engine from known "takeoff" parameters.
PistonEngine(float power, float spd);
+ void setTurboParams(float mul, float maxMP);
void setThrottle(float throttle);
void setMixture(float mixture);
+ float getPower();
+
// Calculates power output and fuel flow, based on a given
// throttle setting (0-1 corresponding to the fraction of
// "available" manifold pressure), mixture (fuel flux per rpm,
// 0-1, where 1 is "max rich", or a little bit more than needed
// for rated power at sea level)
- void calc(float density, float speed,
+ void calc(float pressure, float temp, float speed,
float* powerOut, float* fuelFlowOut);
private:
// Runtime settables:
float _throttle;
float _mixture;
+
+ float _turbo;
+ float _maxMP;
};
}; // namespace yasim
_omega = 52.3;
_dir[0] = 1; _dir[1] = 0; _dir[2] = 0;
+ _variable = false;
+
_prop = prop;
_eng = eng;
_moment = moment;
delete _eng;
}
-float PropEngine::getOmega()
+void PropEngine::setAdvance(float advance)
{
- return _omega;
+ _advance = Math::clamp(advance, 0, 1);
}
-Thruster* PropEngine::clone()
+void PropEngine::setVariableProp(float min, float max)
{
- // FIXME: bloody mess
- PropEngine* p = new PropEngine(_prop, _eng, _moment);
- cloneInto(p);
- p->_prop = new Propeller(*_prop);
- p->_eng = new PistonEngine(*_eng);
- return p;
+ _variable = true;
+ _minOmega = min;
+ _maxOmega = max;
+}
+
+float PropEngine::getOmega()
+{
+ return _omega;
}
void PropEngine::getThrust(float* out)
return _fuelFlow;
}
+void PropEngine::stabilize()
+{
+ float speed = -Math::dot3(_wind, _dir);
+ _eng->setThrottle(_throttle);
+ _eng->setMixture(_mixture);
+
+ if(_variable) {
+ _omega = _minOmega + _advance * (_maxOmega - _minOmega);
+ _prop->modPitch(1e6); // Start at maximum pitch and move down
+ } else {
+ _omega = 52;
+ }
+
+ bool goingUp = false;
+ float step = 10;
+ while(true) {
+ float etau, ptau, dummy;
+ _prop->calc(_rho, speed, _omega, &dummy, &ptau);
+ _eng->calc(_P, _T, _omega, &etau, &dummy);
+ float tdiff = etau - ptau;
+
+ if(Math::abs(tdiff/_moment) < 0.1)
+ break;
+
+ if(tdiff > 0) {
+ if(!goingUp) step *= 0.5;
+ goingUp = true;
+ if(!_variable) _omega += step;
+ else _prop->modPitch(1+(step*0.005));
+ } else {
+ if(goingUp) step *= 0.5;
+ goingUp = false;
+ if(!_variable) _omega -= step;
+ else _prop->modPitch(1-(step*0.005));
+ }
+ }
+}
+
void PropEngine::integrate(float dt)
{
float speed = -Math::dot3(_wind, _dir);
_prop->calc(_rho, speed, _omega,
&thrust, &propTorque);
- _eng->calc(_rho, _omega, &engTorque, &_fuelFlow);
+ _eng->calc(_P, _T, _omega, &engTorque, &_fuelFlow);
// Turn the thrust into a vector and save it
Math::mul3(thrust, _dir, _thrust);
// Euler-integrate the RPM. This doesn't need the full-on
// Runge-Kutta stuff.
- _omega += dt*(engTorque-propTorque)/Math::abs(_moment);
+ float rotacc = (engTorque-propTorque)/Math::abs(_moment);
+ _omega += dt * rotacc;
+
+ // Clamp to a 500 rpm idle. This should probably be settable, and
+ // needs to go away when the startup code gets written.
+ if(_omega < 52.3) _omega = 52.3;
// FIXME: Integrate the propeller governor here, when that gets
// implemented...
// Store the total angular momentum into _gyro
Math::mul3(_omega*_moment, _dir, _gyro);
- // Accumulate the engine torque, it acta on the body as a whole.
+ // Accumulate the engine torque, it acts on the body as a whole.
// (Note: engine torque, not propeller torque. They can be
// different, but the difference goes to accelerating the
// rotation. It is the engine torque that is felt at the shaft
// and works on the body.)
float tau = _moment < 0 ? engTorque : -engTorque;
Math::mul3(tau, _dir, _torque);
+
+ // Play with the variable propeller, but only if the propeller is
+ // vaguely stable alread (accelerating less than 100 rpm/s)
+ if(_variable && Math::abs(rotacc) < 20) {
+ float target = _minOmega + _advance*(_maxOmega-_minOmega);
+ float mod = 1.04;
+ if(target > _omega) mod = 1/mod;
+ float diff = Math::abs(target - _omega);
+ if(diff < 1) mod = 1 + (mod-1)*diff;
+ if(thrust < 0) mod = 1;
+ _prop->modPitch(mod);
+ }
}
}; // namespace yasim
public:
PropEngine(Propeller* prop, PistonEngine* eng, float moment);
virtual ~PropEngine();
-
- virtual Thruster* clone();
+
+ void setAdvance(float advance);
+ void setVariableProp(float min, float max);
+
+ virtual PropEngine* getPropEngine() { return this; }
+ virtual PistonEngine* getPistonEngine() { return _eng; }
+ virtual Propeller* getPropeller() { return _prop; }
// Dynamic output
virtual void getThrust(float* out);
// Runtime instructions
virtual void integrate(float dt);
+ virtual void stabilize();
float getOmega();
Propeller* _prop;
PistonEngine* _eng;
+ bool _variable;
+ float _advance; // control input, 0-1
+ float _maxOmega;
+ float _minOmega;
+
float _omega; // RPM, in radians/sec
float _thrust[3];
float _torque[3];
+#include <stdio.h>
+
#include "Atmosphere.hpp"
#include "Math.hpp"
#include "Propeller.hpp"
namespace yasim {
Propeller::Propeller(float radius, float v, float omega,
- float rho, float power, float omega0,
- float power0)
+ float rho, float power)
{
// Initialize numeric constants:
- _lambdaPeak = Math::pow(9.0, -1.0/8.0);
- _beta = 1.0/(Math::pow(9.0, -1.0/8.0) - Math::pow(9.0, -9.0/8.0));
+ _lambdaPeak = Math::pow(5.0, -1.0/4.0);
+ _beta = 1.0/(Math::pow(5.0, -1.0/4.0) - Math::pow(5.0, -5.0/4.0));
_r = radius;
_etaC = 0.85; // make this settable?
_J0 = v/(omega*_lambdaPeak);
+ _baseJ0 = _J0;
float V2 = v*v + (_r*omega)*(_r*omega);
_F0 = 2*_etaC*power/(rho*v*V2);
- float stallAngle = 0.25;
- _lambdaS = _r*(_J0/_r - stallAngle) / _J0;
+ _matchTakeoff = false;
+}
- // Now compute a correction for zero forward speed to make the
- // takeoff performance correct.
- float torque0 = power0/omega0;
- float thrust, torque;
- _takeoffCoef = 1;
- calc(Atmosphere::getStdDensity(0), 0, omega0, &thrust, &torque);
- _takeoffCoef = torque/torque0;
+void Propeller::setTakeoff(float omega0, float power0)
+{
+ // Takeoff thrust coefficient at lambda==0
+ _matchTakeoff = true;
+ float V2 = _r*omega0 * _r*omega0;
+ float gamma = _etaC * _beta / _J0;
+ float torque = power0 / omega0;
+ float density = Atmosphere::getStdDensity(0);
+ _tc0 = (torque * gamma) / (0.5 * density * V2 * _F0);
+}
+
+void Propeller::modPitch(float mod)
+{
+ _J0 *= mod;
+ if(_J0 < 0.25*_baseJ0) _J0 = 0.25*_baseJ0;
+ if(_J0 > 4*_baseJ0) _J0 = 4*_baseJ0;
}
+
void Propeller::calc(float density, float v, float omega,
float* thrustOut, float* torqueOut)
{
float torque = 0;
if(lambda > 1) {
lambda = 1.0/lambda;
- torque = (density*V2*_F0*_J0)/(8*_etaC*_beta*(1-_lambdaPeak));
+ torque = (density*V2*_F0*_J0)/(4*_etaC*_beta*(1-_lambdaPeak));
}
// There's an undefined point at 1. Just offset by a tiny bit to
// point number!)
if(lambda == 1) lambda = 0.9999;
- // Compute thrust, remembering to clamp lambda to the stall point
- float lambda2 = lambda < _lambdaS ? _lambdaS : lambda;
- float thrust = (0.5*density*V2*_F0/(1-_lambdaPeak))*(1-lambda2);
-
- // Calculate lambda^8
- float l8 = lambda*lambda; l8 = l8*l8; l8 = l8*l8;
+ // Calculate lambda^4
+ float l4 = lambda*lambda; l4 = l4*l4;
// thrust/torque ratio
- float gamma = (_etaC*_beta/_J0)*(1-l8);
-
- // Correct slow speeds to get the takeoff parameters correct
- if(lambda < _lambdaPeak) {
- // This will interpolate takeoffCoef along a descending from 1
- // at lambda==0 to 0 at the peak, fairing smoothly into the
- // flat peak.
- float frac = (lambda - _lambdaPeak)/_lambdaPeak;
- gamma *= 1 + (_takeoffCoef - 1)*frac*frac;
- }
+ float gamma = (_etaC*_beta/_J0)*(1-l4);
+
+ // Compute a thrust, clamp to takeoff thrust to prevend huge
+ // numbers at slow speeds.
+ float tc = (1 - lambda) / (1 - _lambdaPeak);
+ if(_matchTakeoff && tc > _tc0) tc = _tc0;
+
+ float thrust = 0.5 * density * V2 * _F0 * tc;
if(torque > 0) {
torque -= thrust/gamma;
// numbers for RPM and power (with air speed and density being
// zero and sea level). RPM values are in radians per second, of
// course.
- Propeller(float radius, float v, float omega, float rho, float power,
- float omega0, float power0);
+ Propeller(float radius, float v, float omega, float rho, float power);
+
+ void setTakeoff(float omega0, float power0);
+
+ void modPitch(float mod);
void calc(float density, float v, float omega,
float* thrustOut, float* torqueOut);
private:
float _r; // characteristic radius
float _J0; // zero-thrust advance ratio
- float _lambdaS; // "propeller stall" normalized advance ratio
+ float _baseJ0; // ... uncorrected for prop advance
float _F0; // thrust coefficient
float _etaC; // Peak efficiency
float _lambdaPeak; // constant, ~0.759835;
float _beta; // constant, ~1.48058;
- float _takeoffCoef; // correction to get the zero-speed torque right
+ float _tc0; // thrust "coefficient" at takeoff
+ bool _matchTakeoff; // Does _tc0 mean anything?
};
}; // namespace yasim
d *= 1 + _spoilerPos * (_spoilerDrag - 1);
d *= 1 + _slatPos * (_slatDrag - 1);
- // FIXME: flaps should REDUCE drag when the reduce lift!
- d *= 1 + Math::abs(_flapPos) * (_flapDrag - 1);
+ // Negative flap deflections don't affect drag until their lift
+ // multiplier exceeds the "camber" (cz0) of the surface.
+ float fp = _flapPos;
+ if(fp < 0) {
+ fp = -fp;
+ fp -= _cz0/(_flapLift-1);
+ if(fp < 0) fp = 0;
+ }
+
+ d *= 1 + fp * (_flapDrag - 1);
return d;
}
for(int i=0; i<3; i++) _pos[i] = _wind[i] = 0;
_throttle = 0;
_mixture = 0;
- _propAdvance = 0;
- _rho = 0;
+ _P = _T = _rho = 0;
}
Thruster::~Thruster()
void Thruster::setThrottle(float throttle)
{
- _throttle = throttle;
+ _throttle = Math::clamp(throttle, 0, 1);
}
void Thruster::setMixture(float mixture)
{
- _mixture = mixture;
-}
-
-void Thruster::setPropAdvance(float propAdvance)
-{
- _propAdvance = propAdvance;
+ _mixture = Math::clamp(mixture, 0, 1);
}
void Thruster::setWind(float* wind)
for(int i=0; i<3; i++) _wind[i] = wind[i];
}
-void Thruster::setDensity(float rho)
-{
- _rho = rho;
-}
-
-void Thruster::cloneInto(Thruster* out)
+void Thruster::setAir(float pressure, float temp)
{
- for(int i=0; i<3; i++) {
- out->_pos[i] = _pos[i];
- out->_dir[i] = _dir[i];
- }
- out->_throttle = _throttle;
- out->_mixture = _mixture;
- out->_propAdvance = _propAdvance;
+ _P = pressure;
+ _T = temp;
+ _rho = _P / (287.1 * _T);
}
}; // namespace yasim
namespace yasim {
+class Jet;
+class PropEngine;
+class Propeller;
+class PistonEngine;
+
class Thruster {
public:
Thruster();
virtual ~Thruster();
+ // Typing info, these are the possible sub-type (or sub-parts)
+ // that a thruster might have. Any might return null. A little
+ // clumsy, but much simpler than an RTTI-based implementation.
+ virtual Jet* getJet() { return 0; }
+ virtual PropEngine* getPropEngine() { return 0; }
+ virtual Propeller* getPropeller() { return 0; }
+ virtual PistonEngine* getPistonEngine() { return 0; }
+
// Static data
void getPosition(float* out);
void setPosition(float* pos);
void getDirection(float* out);
void setDirection(float* dir);
- virtual Thruster* clone()=0;
-
// Controls
void setThrottle(float throttle);
void setMixture(float mixture);
- void setPropAdvance(float advance);
// Dynamic output
virtual void getThrust(float* out)=0;
// Runtime instructions
void setWind(float* wind);
- void setDensity(float rho);
+ void setAir(float pressure, float temp);
virtual void integrate(float dt)=0;
+ virtual void stabilize()=0;
protected:
- void cloneInto(Thruster* out);
-
float _pos[3];
float _dir[3];
float _throttle;
float _mixture;
- float _propAdvance;
float _wind[3];
+ float _P;
+ float _T;
float _rho;
};
void Wing::setFlap0(float lval, float rval)
{
+ lval = Math::clamp(lval, -1, 1);
+ rval = Math::clamp(rval, -1, 1);
for(int i=0; i<_flap0Surfs.size(); i++) {
((Surface*)_flap0Surfs.get(i))->setFlap(lval);
if(_mirror) ((Surface*)_flap0Surfs.get(++i))->setFlap(rval);
void Wing::setFlap1(float lval, float rval)
{
+ lval = Math::clamp(lval, -1, 1);
+ rval = Math::clamp(rval, -1, 1);
for(int i=0; i<_flap1Surfs.size(); i++) {
((Surface*)_flap1Surfs.get(i))->setFlap(lval);
if(_mirror) ((Surface*)_flap1Surfs.get(++i))->setFlap(rval);
void Wing::setSpoiler(float lval, float rval)
{
+ lval = Math::clamp(lval, 0, 1);
+ rval = Math::clamp(rval, 0, 1);
for(int i=0; i<_spoilerSurfs.size(); i++) {
((Surface*)_spoilerSurfs.get(i))->setSpoiler(lval);
if(_mirror) ((Surface*)_spoilerSurfs.get(++i))->setSpoiler(rval);
void Wing::setSlat(float val)
{
+ val = Math::clamp(val, 0, 1);
for(int i=0; i<_slatSurfs.size(); i++)
((Surface*)_slatSurfs.get(i))->setSlat(val);
}
+float Wing::getGroundEffect(float* posOut)
+{
+ for(int i=0; i<3; i++) posOut[i] = _base[i];
+ float span = _length * Math::cos(_sweep) * Math::cos(_dihedral);
+ span = 2*(span + Math::abs(_base[2]));
+ return span;
+}
+
void Wing::compile()
{
// Have we already been compiled?
// Compile the thing into a bunch of Surface objects
void compile();
+ // Ground effect information
+ float getGroundEffect(float* posOut);
+
// Query the list of Surface objects
int numSurfaces();
Surface* getSurface(int n);