#include "Glue.hpp"
#include "RigidBody.hpp"
#include "Surface.hpp"
+#include "Rotorpart.hpp"
+#include "Rotorblade.hpp"
#include "Thruster.hpp"
-
#include "Airplane.hpp"
+
namespace yasim {
// gadgets
inline float norm(float f) { return f<1 ? 1/f : f; }
inline float abs(float f) { return f<0 ? -f : f; }
+// Solver threshold. How close to the solution are we trying
+// to get? Trying too hard can result in oscillations about
+// the correct solution, which is bad. Stick this in as a
+// compile time constant for now, and consider making it
+// settable per-model.
+const float STHRESH = 1;
+
+// How slowly do we change values in the solver. Too slow, and
+// the solution converges very slowly. Too fast, and it can
+// oscillate.
+const float SOLVE_TWEAK = 0.3226;
+
Airplane::Airplane()
{
_emptyWeight = 0;
delete (Tank*)_tanks.get(i);
for(i=0; i<_thrusters.size(); i++)
delete (ThrustRec*)_thrusters.get(i);
- for(i=0; i<_gears.size(); i++)
- delete (GearRec*)_gears.get(i);
+ for(i=0; i<_gears.size(); i++) {
+ GearRec* g = (GearRec*)_gears.get(i);
+ delete g->gear;
+ delete g;
+ }
for(i=0; i<_surfs.size(); i++)
delete (Surface*)_surfs.get(i);
- for(i=0; i<_contacts.size(); i++)
- delete[] (float*)_contacts.get(i);
+ for(i=0; i<_contacts.size(); i++) {
+ ContactRec* c = (ContactRec*)_contacts.get(i);
+ delete c->gear;
+ delete c;
+ }
+ for(i=0; i<_solveWeights.size(); i++)
+ delete (SolveWeight*)_solveWeights.get(i);
+ for(i=0; i<_cruiseControls.size(); i++)
+ delete (Control*)_cruiseControls.get(i);
+ for(i=0; i<_approachControls.size(); i++) {
+ Control* c = (Control*)_approachControls.get(i);
+ if(c != &_approachElevator)
+ delete c;
+ }
+ delete _wing;
+ delete _tail;
+ for(i=0; i<_vstabs.size(); i++)
+ delete (Wing*)_vstabs.get(i);
+ for(i=0; i<_weights.size(); i++)
+ delete (WeightRec*)_weights.get(i);
+ for(i=0; i<_rotors.size(); i++)
+ delete (Rotor*)_rotors.get(i);
}
void Airplane::iterate(float dt)
updateGearState();
_model.iterate();
+}
- // FIXME: Consume fuel
+void Airplane::calcFuelWeights()
+{
+ for(int i=0; i<_tanks.size(); i++) {
+ Tank* t = (Tank*)_tanks.get(i);
+ _model.getBody()->setMass(t->handle, t->fill);
+ }
}
ControlMap* Airplane::getControlMap()
return ((GearRec*)_gears.get(g))->gear;
}
+Hook* Airplane::getHook()
+{
+ return _model.getHook();
+}
+
+Launchbar* Airplane::getLaunchbar()
+{
+ return _model.getLaunchbar();
+}
+
void Airplane::updateGearState()
{
for(int i=0; i<_gears.size(); i++) {
}
}
-void Airplane::setApproach(float speed, float altitude)
-{
- // The zero AoA will become a calculated stall AoA in compile()
- setApproach(speed, altitude, 0);
-}
-
-void Airplane::setApproach(float speed, float altitude, float aoa)
+void Airplane::setApproach(float speed, float altitude, float aoa, float fuel)
{
_approachSpeed = speed;
_approachP = Atmosphere::getStdPressure(altitude);
_approachT = Atmosphere::getStdTemperature(altitude);
_approachAoA = aoa;
+ _approachFuel = fuel;
}
-void Airplane::setCruise(float speed, float altitude)
+void Airplane::setCruise(float speed, float altitude, float fuel)
{
_cruiseSpeed = speed;
_cruiseP = Atmosphere::getStdPressure(altitude);
_cruiseT = Atmosphere::getStdTemperature(altitude);
_cruiseAoA = 0;
_tailIncidence = 0;
+ _cruiseFuel = fuel;
}
void Airplane::setElevatorControl(int control)
_cruiseControls.add(c);
}
+void Airplane::addSolutionWeight(bool approach, int idx, float wgt)
+{
+ SolveWeight* w = new SolveWeight();
+ w->approach = approach;
+ w->idx = idx;
+ w->wgt = wgt;
+ _solveWeights.add(w);
+}
+
int Airplane::numTanks()
{
return _tanks.size();
return ((Tank*)_tanks.get(tank))->fill;
}
+float Airplane::setFuel(int tank, float fuel)
+{
+ return ((Tank*)_tanks.get(tank))->fill = fuel;
+}
+
float Airplane::getFuelDensity(int tank)
{
return ((Tank*)_tanks.get(tank))->density;
}
+float Airplane::getTankCapacity(int tank)
+{
+ return ((Tank*)_tanks.get(tank))->cap;
+}
+
void Airplane::setWeight(float weight)
{
_emptyWeight = weight;
_vstabs.add(vstab);
}
+void Airplane::addRotor(Rotor* rotor)
+{
+ _rotors.add(rotor);
+}
+
void Airplane::addFuselage(float* front, float* back, float width,
float taper, float mid)
{
_gears.add(g);
}
+void Airplane::addHook(Hook* hook)
+{
+ _model.addHook(hook);
+}
+
+void Airplane::addLaunchbar(Launchbar* launchbar)
+{
+ _model.addLaunchbar(launchbar);
+}
+
void Airplane::addThruster(Thruster* thruster, float mass, float* cg)
{
ThrustRec* t = new ThrustRec();
int i;
for(i=0; i<_tanks.size(); i++) {
Tank* t = (Tank*)_tanks.get(i);
+ t->fill = frac * t->cap;
_model.getBody()->setMass(t->handle, t->cap * frac);
}
}
void Airplane::addContactPoint(float* pos)
{
- float* cp = new float[3];
- cp[0] = pos[0];
- cp[1] = pos[1];
- cp[2] = pos[2];
- _contacts.add(cp);
+ ContactRec* c = new ContactRec;
+ c->gear = 0;
+ c->p[0] = pos[0];
+ c->p[1] = pos[1];
+ c->p[2] = pos[2];
+ _contacts.add(c);
}
float Airplane::compileWing(Wing* w)
return wgt;
}
+float Airplane::compileRotor(Rotor* r)
+{
+ // Todo: add rotor to model!!!
+ // Todo: calc and add mass!!!
+ r->compile();
+ _model.addRotor(r);
+
+ float wgt = 0;
+ int i;
+ for(i=0; i<r->numRotorparts(); i++) {
+ Rotorpart* s = (Rotorpart*)r->getRotorpart(i);
+
+ _model.addRotorpart(s);
+
+ float mass = s->getWeight();
+ mass = mass * Math::sqrt(mass);
+ float pos[3];
+ s->getPosition(pos);
+ _model.getBody()->addMass(mass, pos);
+ wgt += mass;
+ }
+
+ for(i=0; i<r->numRotorblades(); i++) {
+ Rotorblade* b = (Rotorblade*)r->getRotorblade(i);
+
+ _model.addRotorblade(b);
+
+ float mass = b->getWeight();
+ mass = mass * Math::sqrt(mass);
+ float pos[3];
+ b->getPosition(pos);
+ _model.getBody()->addMass(mass, pos);
+ wgt += mass;
+ }
+ return wgt;
+}
+
float Airplane::compileFuselage(Fuselage* f)
{
// The front and back are contact points
int i;
for(i=0; i<_contacts.size(); i++) {
- float *cp = (float*)_contacts.get(i);
+ ContactRec* c = (ContactRec*)_contacts.get(i);
Gear* g = new Gear();
- g->setPosition(cp);
+ c->gear = g;
+ g->setPosition(c->p);
g->setCompression(comp);
g->setSpring(spring);
void Airplane::compile()
{
- double ground[3];
- ground[0] = 0; ground[1] = 0; ground[2] = 1;
- _model.setGroundPlane(ground, -100000);
-
RigidBody* body = _model.getBody();
int firstMass = body->numMasses();
float aeroWgt = 0;
// The Wing objects
- aeroWgt += compileWing(_wing);
- aeroWgt += compileWing(_tail);
+ if (_wing)
+ aeroWgt += compileWing(_wing);
+ if (_tail)
+ aeroWgt += compileWing(_tail);
int i;
- for(i=0; i<_vstabs.size(); i++) {
+ for(i=0; i<_vstabs.size(); i++)
aeroWgt += compileWing((Wing*)_vstabs.get(i));
- }
+ for(i=0; i<_rotors.size(); i++)
+ aeroWgt += compileRotor((Rotor*)_rotors.get(i));
// The fuselage(s)
- for(i=0; i<_fuselages.size(); i++) {
+ for(i=0; i<_fuselages.size(); i++)
aeroWgt += compileFuselage((Fuselage*)_fuselages.get(i));
- }
// Count up the absolute weight we have
float nonAeroWgt = _ballast;
}
// Ground effect
- float gepos[3];
- float gespan = _wing->getGroundEffect(gepos);
- _model.setGroundEffect(gepos, gespan, 0.3f);
+ if(_wing) {
+ float gepos[3];
+ float gespan = 0;
+ gespan = _wing->getGroundEffect(gepos);
+ _model.setGroundEffect(gepos, gespan, 0.15f);
+ }
solveGear();
- solve();
+ if(_wing && _tail) solve();
+ else solveHelicopter();
// Do this after solveGear, because it creates "gear" objects that
// we don't want to affect.
((GearRec*)_gears.get(i))->wgt /= total;
// The force at max compression should be sufficient to stop a
- // plane moving downwards at 3x the approach descent rate. Assume
+ // plane moving downwards at 2x the approach descent rate. Assume
// a 3 degree approach.
- float descentRate = 3.0f*_approachSpeed/19.1f;
+ float descentRate = 2.0f*_approachSpeed/19.1f;
// Spread the kinetic energy according to the gear weights. This
// will results in an equal compression fraction (not distance) of
// Energy in a spring: e = 0.5 * k * len^2
float k = 2 * e / (len*len);
- gr->gear->setSpring(k);
+ gr->gear->setSpring(k * gr->gear->getSpring());
// Critically damped (too damped, too!)
- gr->gear->setDamping(2*Math::sqrt(k*_approachWeight*gr->wgt));
+ gr->gear->setDamping(2*Math::sqrt(k*_approachWeight*gr->wgt)
+ * gr->gear->getDamping());
// These are pretty generic
gr->gear->setStaticFriction(0.8f);
_model.getThruster(i)->stabilize();
}
+void Airplane::setupWeights(bool isApproach)
+{
+ int i;
+ for(i=0; i<_weights.size(); i++)
+ setWeight(i, 0);
+ for(i=0; i<_solveWeights.size(); i++) {
+ SolveWeight* w = (SolveWeight*)_solveWeights.get(i);
+ if(w->approach == isApproach)
+ setWeight(w->idx, w->wgt);
+ }
+}
+
void Airplane::runCruise()
{
setupState(_cruiseAoA, _cruiseSpeed, &_cruiseState);
_model.setState(&_cruiseState);
- _model.setAir(_cruiseP, _cruiseT);
+ _model.setAir(_cruiseP, _cruiseT,
+ Atmosphere::calcStdDensity(_cruiseP, _cruiseT));
// The control configuration
_controls.reset();
Math::mul3(-1, _cruiseState.v, wind);
Math::vmul33(_cruiseState.orient, wind, wind);
- // Cruise is by convention at 50% tank capacity
- setFuelFraction(0.5);
+ setFuelFraction(_cruiseFuel);
+ setupWeights(false);
// Set up the thruster parameters and iterate until the thrust
// stabilizes.
for(i=0; i<_thrusters.size(); i++) {
Thruster* t = ((ThrustRec*)_thrusters.get(i))->thruster;
t->setWind(wind);
- t->setAir(_cruiseP, _cruiseT);
+ t->setAir(_cruiseP, _cruiseT,
+ Atmosphere::calcStdDensity(_cruiseP, _cruiseT));
}
stabilizeThrust();
updateGearState();
// Precompute thrust in the model, and calculate aerodynamic forces
+ _model.getBody()->recalc();
_model.getBody()->reset();
_model.initIteration();
_model.calcForces(&_cruiseState);
{
setupState(_approachAoA, _approachSpeed, &_approachState);
_model.setState(&_approachState);
- _model.setAir(_approachP, _approachT);
+ _model.setAir(_approachP, _approachT,
+ Atmosphere::calcStdDensity(_approachP, _approachT));
// The control configuration
_controls.reset();
Math::mul3(-1, _approachState.v, wind);
Math::vmul33(_approachState.orient, wind, wind);
- // Approach is by convention at 20% tank capacity
- setFuelFraction(0.2f);
+ setFuelFraction(_approachFuel);
+
+ setupWeights(true);
// Run the thrusters until they get to a stable setting. FIXME:
// this is lots of wasted work.
for(i=0; i<_thrusters.size(); i++) {
Thruster* t = ((ThrustRec*)_thrusters.get(i))->thruster;
t->setWind(wind);
- t->setAir(_approachP, _approachT);
+ t->setAir(_approachP, _approachT,
+ Atmosphere::calcStdDensity(_approachP, _approachT));
}
stabilizeThrust();
updateGearState();
// Precompute thrust in the model, and calculate aerodynamic forces
+ _model.getBody()->recalc();
_model.getBody()->reset();
_model.initIteration();
_model.calcForces(&_approachState);
void Airplane::applyDragFactor(float factor)
{
- float applied = Math::sqrt(factor);
+ float applied = Math::pow(factor, SOLVE_TWEAK);
_dragFactor *= applied;
- _wing->setDragScale(_wing->getDragScale() * applied);
- _tail->setDragScale(_tail->getDragScale() * applied);
+ if(_wing)
+ _wing->setDragScale(_wing->getDragScale() * applied);
+ if(_tail)
+ _tail->setDragScale(_tail->getDragScale() * applied);
int i;
for(i=0; i<_vstabs.size(); i++) {
Wing* w = (Wing*)_vstabs.get(i);
void Airplane::applyLiftRatio(float factor)
{
- float applied = Math::sqrt(factor);
+ float applied = Math::pow(factor, SOLVE_TWEAK);
_liftRatio *= applied;
- _wing->setLiftRatio(_wing->getLiftRatio() * applied);
- _tail->setLiftRatio(_tail->getLiftRatio() * applied);
+ if(_wing)
+ _wing->setLiftRatio(_wing->getLiftRatio() * applied);
+ if(_tail)
+ _tail->setLiftRatio(_tail->getLiftRatio() * applied);
int i;
for(i=0; i<_vstabs.size(); i++) {
Wing* w = (Wing*)_vstabs.get(i);
float tmp[3];
_solutionIterations = 0;
_failureMsg = 0;
+
while(1) {
- if(_solutionIterations++ > 10000) {
+ if(_solutionIterations++ > 10000) {
_failureMsg = "Solution failed to converge after 10000 iterations";
- return;
+ return;
}
// Run an iteration at cruise, and extract the needed numbers:
float thrust = tmp[0];
_model.getBody()->getAccel(tmp);
+ Math::tmul33(_cruiseState.orient, tmp, tmp);
float xforce = _cruiseWeight * tmp[0];
float clift0 = _cruiseWeight * tmp[2];
_model.getBody()->getAngularAccel(tmp);
+ Math::tmul33(_cruiseState.orient, tmp, tmp);
float pitch0 = tmp[1];
// Run an approach iteration, and do likewise
runApproach();
_model.getBody()->getAngularAccel(tmp);
- float apitch0 = tmp[1];
+ Math::tmul33(_approachState.orient, tmp, tmp);
+ double apitch0 = tmp[1];
_model.getBody()->getAccel(tmp);
+ Math::tmul33(_approachState.orient, tmp, tmp);
float alift = _approachWeight * tmp[2];
// Modify the cruise AoA a bit to get a derivative
_cruiseAoA -= ARCMIN;
_model.getBody()->getAccel(tmp);
+ Math::tmul33(_cruiseState.orient, tmp, tmp);
float clift1 = _cruiseWeight * tmp[2];
// Do the same with the tail incidence
_tail->setIncidence(_tailIncidence);
_model.getBody()->getAngularAccel(tmp);
+ Math::tmul33(_cruiseState.orient, tmp, tmp);
float pitch1 = tmp[1];
// Now calculate:
float tailDelta = -pitch0 * (ARCMIN/(pitch1-pitch0));
// Sanity:
- if(dragFactor <= 0) {
- _failureMsg = "Zero or negative drag adjustment.";
- return;
- } else if(liftFactor <= 0) {
- _failureMsg = "Zero or negative lift adjustment.";
- return;
- }
+ if(dragFactor <= 0 || liftFactor <= 0)
+ break;
// And the elevator control in the approach. This works just
// like the tail incidence computation (it's solving for the
// same thing -- pitching moment -- by diddling a different
// variable).
- const float ELEVDIDDLE = 0.0001f;
+ const float ELEVDIDDLE = 0.001f;
_approachElevator.val += ELEVDIDDLE;
runApproach();
_approachElevator.val -= ELEVDIDDLE;
_model.getBody()->getAngularAccel(tmp);
- float apitch1 = tmp[1];
+ Math::tmul33(_approachState.orient, tmp, tmp);
+ double apitch1 = tmp[1];
float elevDelta = -apitch0 * (ELEVDIDDLE/(apitch1-apitch0));
// Now apply the values we just computed. Note that the
applyLiftRatio(liftFactor);
// DON'T do the following until the above are sane
- if(normFactor(dragFactor) > 1.1
- || normFactor(liftFactor) > 1.1)
+ if(normFactor(dragFactor) > STHRESH*1.0001
+ || normFactor(liftFactor) > STHRESH*1.0001)
{
continue;
}
// OK, now we can adjust the minor variables:
- _cruiseAoA += 0.5f*aoaDelta;
- _tailIncidence += 0.5f*tailDelta;
- _approachElevator.val += 0.5f*elevDelta;
+ _cruiseAoA += SOLVE_TWEAK*aoaDelta;
+ _tailIncidence += SOLVE_TWEAK*tailDelta;
- _cruiseAoA = clamp(_cruiseAoA, -0.174f, 0.174f);
- _tailIncidence = clamp(_tailIncidence, -0.174f, 0.174f);
- _approachElevator.val = clamp(_approachElevator.val, -1.f, 1.f);
-
- if(norm(dragFactor) < 1.00001 &&
- norm(liftFactor) < 1.00001 &&
- abs(aoaDelta) < .000017 &&
- abs(tailDelta) < .000017 &&
- abs(elevDelta) < 0.00001)
+ _cruiseAoA = clamp(_cruiseAoA, -0.175f, 0.175f);
+ _tailIncidence = clamp(_tailIncidence, -0.175f, 0.175f);
+
+ if(abs(xforce/_cruiseWeight) < STHRESH*0.0001 &&
+ abs(alift/_approachWeight) < STHRESH*0.0001 &&
+ abs(aoaDelta) < STHRESH*.000017 &&
+ abs(tailDelta) < STHRESH*.000017)
{
- break;
+ // If this finaly value is OK, then we're all done
+ if(abs(elevDelta) < STHRESH*0.0001)
+ break;
+
+ // Otherwise, adjust and do the next iteration
+ _approachElevator.val += SOLVE_TWEAK * elevDelta;
+ if(abs(_approachElevator.val) > 1) {
+ _failureMsg = "Insufficient elevator to trim for approach";
+ break;
+ }
}
}
} else if(_liftRatio < 1e-04 || _liftRatio > 1e4) {
_failureMsg = "Lift ratio beyond reasonable bounds.";
return;
- } else if(Math::abs(_cruiseAoA) >= .174) {
+ } else if(Math::abs(_cruiseAoA) >= .17453293) {
_failureMsg = "Cruise AoA > 10 degrees";
return;
- } else if(Math::abs(_tailIncidence) >= .174) {
+ } else if(Math::abs(_tailIncidence) >= .17453293) {
_failureMsg = "Tail incidence > 10 degrees";
return;
}
}
+
+void Airplane::solveHelicopter()
+{
+ _solutionIterations = 0;
+ _failureMsg = 0;
+
+ applyDragFactor(Math::pow(15.7/1000, 1/SOLVE_TWEAK));
+ applyLiftRatio(Math::pow(104, 1/SOLVE_TWEAK));
+ setupState(0,0, &_cruiseState);
+ _model.setState(&_cruiseState);
+ _controls.reset();
+ _model.getBody()->reset();
+}
+
}; // namespace yasim