That blog does not exist

Game Development Community

Plane Flight Model for WheeledFlyingVehicle

by Brian Richardson · 06/15/2005 (12:23 am) · 14 comments

This is the flight model I made for a project that's not going to be finished. So I thought I'd
publish it and maybe someone else can get some use out of it. This implements a standard thrust, lift, drag, gravity flight model that
a plane uses. It's very simplified, it calculates forces on the plane as a whole. It's been tweaked to "feel right" when flying, so it's
definitely not a flight simulation, but a fun arcade dogfighting flight model.

It hasn't been profiled, so there is plenty of room of optimization (for example, not calling updateForcesDriving when you're far above ground.).
This also doesn't contain driving mode vs. flying mode. It is always checking both. So you can just land your plane BF1942 style.

This adds a bunch of new parameters to the wheeledFlyingVehicle datablock. They're explained below in step one. You
can tweak these parameters to get new flying characteristics to your plane.

Updated 6/27/05, Thanks to Robert Rice for pointing out some variable issues!

Step 0: Install Robert Browsers Wheeled Flying Vehicle Resource. It's available here

In wheeledFlyingVehicle.h:

In class WheeledFlyingVehicleData, add these variables to the public section:

F32 liftCoefficient;			// used in lift calc
	F32 wingArea;					// how much lift surface do we have
	F32 airDensity;				// used in lift calc
	F32 dragCoefficient;			// drag by this amount
	F32 pitchLinForce;			// how much force to apply to pitch immediately based on pitch
	F32 noseDownCutoff;			// below this speed, apply full gravity to torque
	F32 noseDownHigh;				// above this speed, don't apply any gravity to torque
	F32 maxForwardVelocity;		// don't apply anymore thrust if we are going this fast.
	F32 steeringDecay;			// Scale the steering by this amount every tick, effective moves the stick to zero
   F32 maneuverCutoff;			// Below this speed, you can not pitch/roll
	F32 manueverHigh;				// Above this speed, you have full manuever ability
        F32 propThrustCoeff;             // How much thrust does the propellor generate
        F32 maneuveringForce;          // How much force to apply when changing direction

In class WheeledFlyingVehicle, add this variable to the private section:

F32 mSteeringRoll;

and add these new methods:

void updateForcesFlying(F32 dt);
	void updateForcesDriving(F32 dt);
	F32 inputAdjust(const F32 inputAmt);

In wheeledFlyingVehicle.cc:

Add this to the end of WheeledFlyingVehicleData::WheeledFlyingVehicleData()

liftCoefficient = 2.8f;
	wingArea = 16.0f;
	airDensity = 0.1f;
	dragCoefficient = 0.4f;
	pitchLinForce = 0.3f;
	noseDownCutoff = 50.0f;		// below this speed, apply full gravity to torque
	noseDownHigh = 200.0f;			// above this speed, don't apply any gravity to torque
	maxForwardVelocity = 3200.0f;	// don't apply anymore thrust if we are going this fast.
	steeringDecay = 0.95f;		// Scale the steering by this amount every tick, effective moves the stick to zero
	maneuverCutoff = 15.0f;				// Below this speed, you can not pitch/roll
	manueverHigh = 65.0f;				   // Above this speed, you have full manuever ability
        propThrustCoeff = 0.3;
        maneuveringForce = 120000;


Add this to the end of WheeledFlyingVehicleData::initPersistFields()

addField("maneuveringForce", TypeF32, Offset(maneuveringForce, WheeledFlyingVehicleData));
   addField("liftCoefficient", TypeF32, Offset(liftCoefficient, WheeledFlyingVehicleData));
   addField("wingArea", TypeF32, Offset(wingArea, WheeledFlyingVehicleData));
   addField("airDensity", TypeF32, Offset(airDensity, WheeledFlyingVehicleData));
   addField("dragCoefficient", TypeF32, Offset(dragCoefficient, WheeledFlyingVehicleData));
   addField("pitchLinForce", TypeF32, Offset(pitchLinForce, WheeledFlyingVehicleData));
   addField("noseDownCutoff", TypeF32, Offset(noseDownCutoff, WheeledFlyingVehicleData)); 		
   addField("noseDownHigh", TypeF32, Offset(noseDownHigh, WheeledFlyingVehicleData)); 
   addField("maxForwardVelocity", TypeF32, Offset(maxForwardVelocity, WheeledFlyingVehicleData)); 
   addField("steeringDecay", TypeF32, Offset(steeringDecay, WheeledFlyingVehicleData)); 
   addField("propThrustCoeff", TypeF32, Offset(propThrustCoeff, WheeledFlyingVehicleData));

Add this to the end of WheeledFlyingVehicleData::packData()

stream->write(maneuveringForce);
   stream->write(liftCoefficient);
   stream->write(wingArea);
   stream->write(airDensity);
   stream->write(dragCoefficient);
   stream->write(pitchLinForce);
   stream->write(noseDownCutoff); 
   stream->write(noseDownHigh); 
   stream->write(maxForwardVelocity); 
   stream->write(steeringDecay); 
   stream->write(propThrustCoeff);

Add this to the end of WheeledFlyingVehicleData::unpackData()

stream->read(&maneuveringForce);
   stream->read(&liftCoefficient);
   stream->read(&wingArea);
   stream->read(&airDensity);
   stream->read(&dragCoefficient);
   stream->read(&pitchLinForce);
   stream->read(&noseDownCutoff); 
   stream->read(&noseDownHigh); 
   stream->read(&maxForwardVelocity); 
   stream->read(&steeringDecay); 
   stream->read(&propThrustCoeff);

Add this line to WheeledFlyingVehicle::WheeledFlyingVehicle:

mSteeringRoll = 0;

Replace WheeledFlyingVehicle::updateMove with this:

void WheeledFlyingVehicle::updateMove(const Move* move)
{
	Move newMove(*move);
	newMove.pitch *= -1;

   Parent::updateMove(&newMove);

   // Roll 
	mSteeringRoll = mClampF(mSteeringRoll + move->roll,-mDataBlock->maxSteeringAngle,
						mDataBlock->maxSteeringAngle);

	if (move == &NullMove)
		mSteering.set(0,0);

   // Check the mission area to get the factor for the flight ceiling
	mCeilingFactor = 1.0f; 
   MissionArea * obj = dynamic_cast<MissionArea*>(Sim::findObject("MissionArea"));
   if (obj != NULL)
   { 
      F32 flightCeiling = obj->getFlightCeiling();
      F32 ceilingRange  = obj->getFlightCeilingRange();

      if (mRigid.linPosition.z > flightCeiling)
      {
         // Thrust starts to fade at the ceiling, and is 0 at ceil + range
         if (ceilingRange == 0)
         {
            mCeilingFactor = 0;
         }
         else
         {
            mCeilingFactor = 1.0f - ((mRigid.linPosition.z - flightCeiling) / (flightCeiling + ceilingRange));
            if (mCeilingFactor < 0.0f)
               mCeilingFactor = 0.0f;
         }
      }
	} 

   if (mCeilingFactor != 1.0f)
      mJetting = false;

   mThrust.x = move->x;
   mThrust.y = mClampF(mThrust.y + move->y, 0, 1);

   mSteering.x *= mDataBlock->steeringDecay;
	mSteering.y *= mDataBlock->steeringDecay;
	mSteeringRoll *= mDataBlock->steeringDecay;
	mThrust.y *= 0.985;
	if (mThrust.y < 0.1)
		mThrust.y = 0;
	if (getDamageValue() > 0.99)
		mThrust.y = 0;
}

Replace WheeledFlyingVehicle::updateForces with this:

void WheeledFlyingVehicle::updateForces(F32 dt)
{
	if (getDamageValue() < 0.99f) {
		updateForcesDriving(dt);
		updateForcesFlying(dt);
	} else {		
		mRigid.force = Point3F(0, 0, sWheeledFlyingVehicleGravity * mRigid.mass * mGravityMod);
		mRigid.torque = Point3F(0, 0, 0);
	}
}

void WheeledFlyingVehicle::updateForcesDriving(F32 dt)
{
   extendWheels();

   F32 oneOverSprungMass = 1 / (mMass * 0.8);
   F32 aMomentum = mMass / mDataBlock->wheelCount;

   // Get the current matrix and extact vectors
   MatrixF currMatrix;
   mRigid.getTransform(&currMatrix);

   Point3F bx,by,bz;
   currMatrix.getColumn(0,&bx);
   currMatrix.getColumn(1,&by);
   currMatrix.getColumn(2,&bz);

   // Steering angles from current steering wheel position
   F32 quadraticSteering = -(mSteering.x * mFabs(mSteering.x));
   F32 cosSteering,sinSteering;

   mSinCos(quadraticSteering, sinSteering, cosSteering);

   // Calculate Engine and brake torque values used later by in
   // wheel calculations.
   F32 engineTorque,brakeVel;
   if (mBraking) {
      brakeVel = (mDataBlock->brakeTorque / aMomentum) * dt;
      engineTorque = 0;
   }
   else {
      if (mThrottle) {
         engineTorque = mDataBlock->engineTorque * mThrottle;
         brakeVel = 0;
         // Double the engineTorque to help out the jets
         if (mThrottle > 0 && mJetting)
            engineTorque *= 2;
      }
      else {
         // Engine break.
         brakeVel = (mDataBlock->engineBrake / aMomentum) * dt;
         engineTorque = 0;
      }
   }

   // Integrate forces, we'll do this ourselves here instead of
   // relying on the rigid class which does it during movement.
   Wheel* wend = &mWheel[mDataBlock->wheelCount];
   mRigid.force.set(0, 0, 0);
   mRigid.torque.set(0, 0, 0);

   // Calculate vertical load for friction.  Divide up the spring
   // forces across all the wheels that are in contact with
   // the ground.
   U32 contactCount = 0;
   F32 verticalLoad = 0;
   for (Wheel* wheel = mWheel; wheel < wend; wheel++) {
      if (wheel->tire && wheel->spring && wheel->surface.contact) {
         verticalLoad += wheel->spring->force * (1 - wheel->extension);
         contactCount++;
      }
   }
   if (contactCount)
      verticalLoad /= contactCount;

   // Sum up spring and wheel torque forces
   for (Wheel* wheel = mWheel; wheel < wend; wheel++)  {
      if (!wheel->tire || !wheel->spring)
         continue;

      F32 Fy = 0;
      if (wheel->surface.contact) {

         // First, let's compute the wheel's position, and worldspace velocity
         Point3F pos, r, localVel;
         currMatrix.mulP(wheel->data->pos, &pos);
         mRigid.getOriginVector(pos,&r);
         mRigid.getVelocity(r, &localVel);

         // Spring force & damping
         F32 spring  = wheel->spring->force * (1 - wheel->extension);
         F32 damping = wheel->spring->damping * -(mDot(bz, localVel) / wheel->spring->length);
         if (damping < 0)
            damping = 0;

         // Anti-sway force based on difference in suspension extension
         F32 antiSway = 0;
         if (wheel->data->opposite != -1) {
            Wheel* oppositeWheel = &mWheel[wheel->data->opposite];
            if (oppositeWheel->surface.contact)
               antiSway = ((oppositeWheel->extension - wheel->extension) *
                  wheel->spring->antiSway);
            if (antiSway < 0)
               antiSway = 0;
         }

         // Spring forces act straight up and are applied at the
         // spring's root position.
         Point3F t, forceVector = bz * (spring + damping + antiSway);
         mCross(r, forceVector, &t);
         mRigid.torque += t;
         mRigid.force += forceVector;

         // Tire direction vectors perpendicular to surface normal
         Point3F wheelXVec = bx * cosSteering;
         wheelXVec += by * sinSteering * wheel->steering;
         Point3F tireX, tireY;
         mCross(wheel->surface.normal, wheelXVec, &tireY);
         tireY.normalize();
         mCross(tireY, wheel->surface.normal, &tireX);
         tireX.normalize();

         // Velocity of tire at the surface contact
         Point3F wheelContact, wheelVelocity;
         mRigid.getOriginVector(wheel->surface.pos,&wheelContact);
         mRigid.getVelocity(wheelContact, &wheelVelocity);
	         F32 xVelocity = mDot(tireX, wheelVelocity);
         F32 yVelocity = mDot(tireY, wheelVelocity);

         // Tires act as springs and generate lateral and longitudinal
         // forces to move the vehicle. These distortion/spring forces
         // are what convert wheel angular velocity into forces that
         // act on the rigid body.

         // Longitudinal tire deformation force
         F32 ddy = (wheel->avel * wheel->tire->radius - yVelocity) -
            wheel->tire->longitudinalRelaxation *
            mFabs(wheel->avel) * wheel->Dy;
         wheel->Dy += ddy * dt;
         Fy = (wheel->tire->longitudinalForce * wheel->Dy +
            wheel->tire->longitudinalDamping * ddy);

         // Lateral tire deformation force
         F32 ddx = xVelocity - wheel->tire->lateralRelaxation *
            mFabs(wheel->avel) * wheel->Dx;
         wheel->Dx += ddx * dt;
         F32 Fx = -(wheel->tire->lateralForce * wheel->Dx +
            wheel->tire->lateralDamping * ddx);

         // Vertical load on the tire
         verticalLoad = spring + damping + antiSway;
         if (verticalLoad < 0)
            verticalLoad = 0;

         // Adjust tire forces based on friction
         F32 surfaceFriction = 1;
         F32 mu = surfaceFriction * (wheel->slipping)?
            wheel->tire->kineticFriction:
            wheel->tire->staticFriction;
         F32 Fn = verticalLoad * mu; Fn *= Fn;
         F32 Fw = Fx * Fx + Fy * Fy;
         if (Fw > Fn) {
            F32 K = mSqrt(Fn / Fw);
            Fy *= K;
            Fx *= K;
            wheel->Dy *= K;
            wheel->Dx *= K;
            wheel->slip = 1 - K;
            wheel->slipping = true;
         }
         else {
            wheel->slipping = false;
            wheel->slip = 0;
         }

         // Tire forces act through the tire direction vectors parallel
         // to the surface and are applied at the wheel hub.
         forceVector = (tireX * Fx) + (tireY * Fy);
         pos -= bz * (wheel->spring->length * wheel->extension);
         mRigid.getOriginVector(pos,&r);
         mCross(r, forceVector, &t);
         mRigid.torque += t;
         mRigid.force += forceVector;
      }
      else {
         // Wheel not in contact with the ground
         wheel->torqueScale  = 0;
         wheel->slip = 0;

         // Relax the tire deformation
         wheel->Dy += (-wheel->tire->longitudinalRelaxation *
                        mFabs(wheel->avel) * wheel->Dy) * dt;
         wheel->Dx += (-wheel->tire->lateralRelaxation *
                        mFabs(wheel->avel) * wheel->Dx) * dt;
      }

      // Adjust the wheel's angular velocity based on engine torque
      // and tire deformation forces.
      if (wheel->powered) {
         F32 maxAvel = mDataBlock->maxWheelSpeed / wheel->tire->radius;
         wheel->torqueScale = (mFabs(wheel->avel) > maxAvel) ? 0 :
            1 - (mFabs(wheel->avel) / maxAvel);
      }
      else
         wheel->torqueScale = 0;
      wheel->avel += (((wheel->torqueScale * engineTorque) - Fy *
         wheel->tire->radius) / aMomentum) * dt;

      // Adjust the wheel's angular velocity based on break torque.
      // This is done after avel update to make sure we come to a
      // complete stop.
      if (brakeVel > mFabs(wheel->avel))
         wheel->avel = 0;
      else
         if (wheel->avel > 0)
            wheel->avel -= brakeVel;
         else
            wheel->avel += brakeVel;
   }

   // Jet Force
   if (mJetting)
      mRigid.force += by * mDataBlock->jetForce;

   // Container drag & buoyancy
   mRigid.force  += Point3F(0, 0, -mBuoyancy * sWheeledFlyingVehicleGravity * mRigid.mass);
   mRigid.force  -= mRigid.linVelocity * mDrag;
   mRigid.torque -= mRigid.angMomentum * mDrag;

   // If we've added anything other than gravity, then we're no
   // longer at rest. Could test this a little more efficiently...
   if (mRigid.atRest && (mRigid.force.len() || mRigid.torque.len()))
      mRigid.atRest = false;

   // Integrate and update velocity
   mRigid.linMomentum += mRigid.force * dt;
   mRigid.angMomentum += mRigid.torque * dt;
   mRigid.updateVelocity();

   // Since we've already done all the work, just need to clear this out.
   mRigid.force.set(0, 0, 0);
   mRigid.torque.set(0, 0, 0);

   // If we're still atRest, make sure we're not accumulating anything
   if (mRigid.atRest)
      mRigid.setAtRest();
}

F32 WheeledFlyingVehicle::inputAdjust(const F32 inputAmt)
{
	if (inputAmt != 0) {
		return (inputAmt*inputAmt) * (inputAmt / mFabs(inputAmt));
	} else {
		return 0;
	}
}

void WheeledFlyingVehicle::updateForcesFlying(F32 dt)
{
	// Get our orientation
	MatrixF currPosMat;
	mRigid.getTransform(&currPosMat);
	mRigid.atRest = false;

	Point3F massCenter;
	currPosMat.mulP(mDataBlock->massCenter,&massCenter);

	Point3F xv,yv,zv;
	currPosMat.getColumn(0,&xv);
	currPosMat.getColumn(1,&yv);
	currPosMat.getColumn(2,&zv);
	F32 speed = mRigid.linVelocity.len();
	F32 forwardVelocity = mDot(mRigid.linVelocity, yv);

	// Force 1:  Gravity (linear)
	F32 downForceMag = sWheeledFlyingVehicleGravity * mRigid.mass * mGravityMod;
   Point3F force(0, 0, downForceMag);

	// Force 2:  Thrust
	if ((forwardVelocity < mDataBlock->maxForwardVelocity) && (mThrust.y > 0))
		force += yv * (mThrust.y * mDataBlock->maneuveringForce * mCeilingFactor);

	// Force 3:  Lift = (LiftCoEff * (0.5 * airDensity * velocity^2 * wing area)
	F32 fv = forwardVelocity*forwardVelocity;
	F32 liftDrag = (0.5 * mDataBlock->airDensity * fv * mDataBlock->wingArea);
	F32 liftForce = mDataBlock->liftCoefficient * liftDrag;
	force += zv * liftForce;

	F32 tUp = mClampF(mFabs(liftForce / downForceMag), 0, 1);
	tailUpTarget = tUp * tUp;
	
	// Force 1:  Gravity (angular) 
	Point3F torque(0, 0, 0);
	F32 cutoffPoint = 20;
	F32 positiveLift = liftForce + downForceMag;
	if (positiveLift < cutoffPoint) {
		F32 noseDown = mDot(yv, Point3F(0, 0, -1));
		F32 noseDownCoEff = cutoffPoint - positiveLift;
		noseDownCoEff = mClampF(noseDownCoEff / 100000, 0, 1);
		mCross(massCenter - mRigid.linPosition, Point3F(0, 0, (1 - noseDown) * downForceMag * noseDownCoEff), torque);
	}

  	// Force 4:  Drag
	F32 dragForce = (mDataBlock->dragCoefficient * (mRigid.linVelocity.magnitudeSafe()*mRigid.linVelocity.magnitudeSafe()));
	force -= dragForce * mRigid.linVelocity;
	torque -= mRigid.angMomentum * mDataBlock->rotationalDrag;

	// We can't have full pitch contribution if we are not flying quick enough.
	F32 manueverCoeff = 1.0f;
	if (forwardVelocity <= mDataBlock->maneuverCutoff) {
		manueverCoeff = 0.0f;
	} else {
		F32 range = mDataBlock->manueverHigh - mDataBlock->maneuverCutoff;
		manueverCoeff = mClampF((forwardVelocity - mDataBlock->maneuverCutoff) / range, 0.0f, 1.0f);
	}

	// Pitch control (up/down)
	F32 pitchAmt = inputAdjust(mSteering.y / mDataBlock->maxSteeringAngle); // normalize angle 0..1
   torque -= xv * pitchAmt * mDataBlock->steeringForce * manueverCoeff;
	force -= zv * pitchAmt * mDataBlock->pitchLinForce;  // let's immediately apply some force here

	// Roll control
	F32 rollAmt = inputAdjust(mSteeringRoll / mDataBlock->maxSteeringAngle);
	torque += yv * rollAmt * mDataBlock->steeringRollForce * manueverCoeff;

	// Yaw control
	F32 yawAmt = inputAdjust(mSteering.x / mDataBlock->maxSteeringAngle);
	torque += zv * yawAmt * mDataBlock->steeringForce * manueverCoeff;

	// Let's update our move stuff
	mRigid.force  = force;
	mRigid.torque = torque;
}
Finally, add this stuff to the end of your default.bind.cs file:

function moveleft(%val)
{
$mvLeftAction = %val;
}

function moveright(%val)
{
$mvRightAction = %val;
}

function moveforward(%val)
{
$mvForwardAction = %val;
}

function movebackward(%val)
{
$mvBackwardAction = %val;
}

function rollLeft( %val )
{
$mvRollLeftSpeed = %val ? $Pref::Input::KeyboardTurnSpeed : 0;  
}

function rollRight( %val )
{
$mvRollRightSpeed = %val ? $Pref::Input::KeyboardTurnSpeed : 0;
}

function turnLeft( %val )
{
$mvYawLeftSpeed = %val ? $Pref::Input::KeyboardTurnSpeed : 0;  
}

function turnRight( %val )
{
$mvYawRightSpeed = %val ? $Pref::Input::KeyboardTurnSpeed : 0;
}

function panUp( %val )
{
$mvPitchDownSpeed = %val ? $Pref::Input::KeyboardTurnSpeed : 0;
}

function panDown( %val )
{
$mvPitchUpSpeed = %val ? $Pref::Input::KeyboardTurnSpeed : 0;
}

// Mouse steering functions
//
function getMouseAdjustAmount(%val)
{
   // based on a default camera fov of 90'
return(%val * ($cameraFov / 90) * 0.0025);
}

function yaw(%val)
{
$mvYaw += getMouseAdjustAmount(%val);
}

function pitch(%val)
{
$mvPitch += getMouseAdjustAmount(%val);
}

function roll(%val) 
{
$mvRoll += getMouseAdjustAmount(%val);
}

// We want different behavior for the keys and mouse in game then in editor mode.
function bindGame()
{
	moveMap.bind(keyboard, left, rollLeft);
	moveMap.bind(keyboard, right, rollRight);
	moveMap.bind( mouse, xaxis, roll );
	moveMap.bind(keyboard, a, turnLeft);
	moveMap.bind(keyboard, d, turnRight);
}

function bindEditor()
{
	moveMap.bind(keyboard, left, turnRight);
	moveMap.bind(keyboard, right, turnLeft);
	moveMap.bind(mouse, xaxis, yaw );
   moveMap.bind(keyboard, a, moveLeft);
   moveMap.bind(keyboard, d, moveRight);
}

bindGame();

// These binding do not change 
moveMap.bind( keyboard, w, moveforward );
moveMap.bind( keyboard, s, movebackward );
moveMap.bind( mouse, yaxis, pitch );

moveMap.bind(keyboard, up, panUp);
moveMap.bind(keyboard, down, panDown);

See the bindGame(), bindEditor() functions? I ran into an interesting bug where the editor would turn the opposite of what you
wanted it to. I'm sure this is a bug in my code somewhere, but I didn't get around to fixing it. Post a solution in the comments if you
find one!

Finally, here's an example datablock:

datablock WheeledFlyingVehicleData(AirCraft)
{
	spawnOffset			= "0 0 2";
	emap				= true;
	category			= "Vehicles";
	shapeFile			= "~/data/shapes/vehicles/dev_aircraft/dev_aircraft.dts";
	multipassenger			= false;
	computeCRC			= true;
							
	debrisShapeName		= "~/data/shapes/vehicles/dev_aircraft/dev_aircraft.dts";
	debris			    = AircraftDebris;
	renderWhenDestroyed = false;
	debris = PlaneDebris;
	className = "Plane";
   emap = true;
	explosion = PlaneWeaponExplosion;

   mountPose[0] = sitting;

   maxDamage = 1.5;
   destroyedLevel = 1.0;

   maxSteeringAngle = 0.785;  // Maximum steering angle, should match animation
   integration = 6;           // Force integration time: TickSec/Rate

   // 3rd person camera settings
   cameraRoll = false;         // Roll the camera with the vehicle
   cameraMaxDist = 5;         // Far distance from vehicle
   cameraOffset = 1.5;        // Vertical offset from camera mount point
   cameraLag = 0.00;           // Velocity lag of camera
   cameraDecay = 0.00;        // Decay per sec. rate of velocity lag

   bodyFriction = 9.4;
   bodyRestitution = 0.1;
   minImpactSpeed = 5;        // Impacts over this invoke the script callback
   softImpactSpeed = 5;       // Play SoftImpact Sound
   hardImpactSpeed = 15;      // Play HardImpact Sound

   // Engine
   engineTorque = 65000;       // Engine power
   engineBrake = 1000;         // Braking when throttle is 0
   brakeTorque = 20000;        // When brakes are applied
   maxWheelSpeed = 150;        // Engine scale by current speed / max speed

   // Energy
   //maxEnergy = 100;
   jetForce = 3000;
   minJetEnergy = 30;
   jetEnergyDrain = 2;

   // Sounds
   engineSound = PlaneEngineSound;
  spawnOffset = "0 0 1";

  ultipassenger = false;
  ComputeCRC = true;
  renderWhenDestroyed = false;
  mountPose[0] = sitting;
  drag = 0.15;
  density = 1.0;

  minMountDist = 6;

  //maxDamage = 90.00;
  //DestroyedLevel = 90.00;
  isShielded = true;
  EnergyPerDamagePoint = 160;

  MaxEnergy = 280;    // Afterburner and any energy weapon pool
  MinDrag = 88;       // Linear Drag (eventually slows you down when not thrusting...constant drag)

  RechargeRate = 5.0;

  maxAutoSpeed = 4;      // Autostabilizer kicks in when less than this speed. (meters/second)
  autoAngularForce = 100;  // Angular stabilizer force (this force levels you out when autostabilizer kicks in)
  autoLinearForce = 300;   // Linear stabilzer force (this slows you down when autostabilizer kicks in)
  autoInputDamping = 1.0;// Dampen control input so you don't whack out at very slow speeds

  // Maneuvering Information
  // RFB -> need a steering angle specific to flight since flight and driving steering are so different
  maxFlightSteeringAngle = 0.785; // lower numbers are more manueverable
  //MaxSteeringAngle = 4.2;      // Max radians you can rotate the wheel. Smaller number is more maneuverable.
  HorizontalSurfaceForce = 350;// Horizontal center "wing" (provides "bite" into the wind for climbing/diving and turning)
  VerticalSurfaceForce = 100;  // Vertical center "wing" (controls side slip. lower numbers make MORE slide.)

// New flight model parameters
	mass = 2203;
	massCenter = "0.0 1.0 0";
	liftCoefficient = 2.0;	       // Lift coefficient
	wingArea = 16;				   // Wing area (used in lift calc)
	airDensity = 1;			   // Air density (used in lift calc)
	dragCoefficient = 0.3;		   // Drag
	RotationalDrag = 3;			// Anguler Drag (dampens the drift after you stop moving the mouse...also tumble drag)
	ManeuveringForce = 120000;     // Thrust forvce
	SteeringForce = 60000;      // Used for pitch/yaw
	SteeringRollForce = 200000; // Roll force
    pitchLinForce = 0;		// Apply this amount of force based on pitch
	noseDownCutoff = 500;		// below this speed, apply full gravity to torque
	noseDownHigh = 1000;			// above this speed, don't apply any gravity to torque
	maxForwardVelocity = 3200;	// don't apply anymore thrust if we are going this fast.
    steeringDecay = 0.95;		// Scale the steering by this amount every tick, effective moves the stick to zero
// End

   collisionTol = 0.1;        // Collision distance tolerance
   contactTol = 0.1;          // Contact velocity tolerance

	RollForce = 820;             // Auto-roll (self-correction to right you after you roll/invert)
	HoverHeight = 1.0;           // Height off the ground at rest
	CreateHoverHeight = 1.0;     // Height off the ground when created

  // Rigid body
  //Mass = 500;           // Mass of the vehicle
  //BodyFriction = 0;     // Don't mess with this.
  //BodyRestitution = 0.8;// When you hit the ground, how much you rebound. (between 0 and 1)
  MinRollSpeed = 0;     // Don't mess with this.
  MinImpactSpeed = 0;   // If hit ground at speed above this then it's an impact. Meters/second
  SoftImpactSpeed = 10; // Sound hooks. This is the soft hit.
  HardImpactSpeed = 25; // Sound hooks. This is the hard hit.
  SpeedDamageScale = 0.04;

  collDamageThresholdVel = 20.0;
  collDamageMultiplier = 0.02;

	// Contrail
	minTrailSpeed = 75; // The speed your contrail shows up at.
	TrailEmitter = ContrailEmitter;
	TrailEmitterOffset = "0.0 0.0 0.0";
	TrailEmitterHeight = 0;
	TrailEmitterFreqMod = 2.0;

	// Damage
	damageEmitter[0] = DamageEmitter3;
	damageEmitter[1] = DamageEmitter2;
	damageEmitter[2] = DamageEmitter1;
	damageEmitterOffset[0] = "0.0 -3.0 0.0 ";
	damageEmitterOffset[1] = "0.0 0.0 -1.0";
	damageEmitterOffset[2] = "0.0 1.0 1.0";
	damageLevelTolerance[0] = 0.1;
	damageLevelTolerance[1] = 0.5;
	numDmgEmitterAreas = 3;

   // Inventory
   maxInv[PlaneAmmo] = 500;

//------------------------------------------------------------
// Sound Velocities
// Velocities at which Water Impact sounds will be triggered

  SoftSplashSound = 15.0;
  MediumSplashSoundVelocity = 30.0;
  HardSplashSoundVelocity = 60.0;
  ExitSplashSoundVelocity = 20.0;


   // RFB -> added
   modeChangeEnergyDrain = 30;       // Energy per mode change
   minModeChangeEnergy = 0;
   minModeChangeSpeed = 0;
   maxModeChangeSpeed = 999;
   modeChangeDelay = 30;             // Delay time in ticks
   createMode = 0;
   // <- RFB
};

I just ripped this out of the project I had it working in. So I maybe missing a piece or two, just comment here if you run into any
issues and I'll do my best to help you out.

#1
06/15/2005 (6:20 am)
I have not tried this yet, but you can be sure I will. If this works as intended, then....dude, that's awesome!
#2
06/15/2005 (7:00 am)
I'll have to give this a try... looks very promising.

Thanks for the resource!

Tim
#3
06/15/2005 (7:09 am)
This looks to be a great resource. However, when I install Robert Browsers Wheeled Flying Vehicle Resource I just get a static shape (added through World Creator). Can anyone give me a clue what do next, or what I have done wrong?

Thanks!!
#4
06/15/2005 (7:12 am)
Steve,

If you followed the resource correctly, it probably is not a static shape. First double check your changes to make sure you followed the resource correctly. If you feel as though you did everything correct try this...

Before you launch the mission go into options and controls. Make sure that the thrust and brake are mapped to keys... then launch the mission.

When you move the mouse, do the wheels move?

Tim
#5
06/16/2005 (1:54 am)
@Tim

Managed to get Robert Browsers Wheeled Flying Resource working with some messing about last night. Thanks!!
#6
06/23/2005 (9:56 pm)
So sweet! Nice job robert and bzzt.
#7
06/25/2005 (9:07 am)
Has anyone gotten this to work? I got nothing but 1/2 dozen undeclared identifier errors.
#8
06/26/2005 (7:12 am)
@Robert,

Mail me (bzzt@knowhere.net) your error log so I can see what I need to add to the resource.
#9
06/26/2005 (7:14 am)
@Robert,
I think it would be important to note what version of TGE you are using. 1.3? 1.4(head)?

I got it to work fine in TGE 1.3
#10
06/26/2005 (9:46 am)
Brian, I just sent you an email with the changes I made to get it to compile, along with another question. Thanks!
#11
11/15/2005 (9:10 am)
When implementing this Mod, I ran into an error:

Compiling...
wheeledFlyingVehicle.cc
C:\Torque\SDK\engine\game\vehicles\wheeledFlyingVehicle.cc(1527) : error C2065: 'tailUpTarget' : undeclared identifier
Error executing cl.exe.

torqueDemo.exe - 1 error(s), 0 warning(s)

Which corresponds to this line:
tailUpTarget = tUp * tUp;

I searched the .h and .cc file and did not find this tailUpTarget anywhere.
#12
04/25/2006 (8:12 pm)
Did anyone get this working with 1.4, if so please could you add the changed .cc and .h file resource here, or add it as a zip.. I havent been able to get this working with the wheeledFyingV resource as of yet, but Im not so good at the engine changes...Many thanks in Advance..
#13
06/28/2006 (6:27 am)
Hi people, I got my Wheeled Flying Vehicle flying.
Now i want to use it as a fighter plane and add weopon to its both wings.
can any one please help me out in this matter.
#14
01/28/2007 (6:52 am)
Sorry, just tried this and managed to get it to work with a few minor hacks. The static shape issue is something with the datablock. I managed to fix it, but the physics and workings of the plane IMHO were worse than the original. :(

Now if i thrust up, I am at max speed instantly and cannot control the plane well at all.