aeronautic

Flying it

Drive the flight every frame from a flight model: load factor, roll and sideslip.

The vapor comes and goes with the flight. Lift, and with it every vortex and every suction peak, follows from the airspeed and the angle of attack, so those are the two dials a scene writes every frame.

From a shared flight

Inside a <FlightProvider>, the vapor reads the flight's airspeed, angle of attack, sideslip, altitude, day and humidity on its own, whenever the flight changes, without a React render. The plumes and the control surfaces read the same flight:

const flight = useFlightStore();

useFrame(() => {
  flight.set({
    airspeedMPerS: aircraft.airspeed,
    angleOfAttackRad: aircraft.alpha,
    altitudeM: aircraft.position.y,
  });
});

<WingVapor airframe={airframe} />;

The flight wins over flight and air for what it holds. The altitude is read to the nearest 10 m, so a climb rewrites the condensation table every 10 m rather than every frame. source picks another flight, and source={null} flies only the props.

The flight's sideslip is from the right, as a flight model gives it; the vapor's own sideslipRad is from +Z, the left, so it is turned round on the way in.

Through the ref

<WingVapor>'s ref is the WingVaporHandle, the WingVapor class underneath. Its update* methods change the fields given and keep the rest, without a React render:

const vapor = useRef<WingVaporHandle>(null);

useFrame(() => {
  vapor.current?.updateFlight({
    airspeedMPerS: aircraft.airspeed,
    angleOfAttackRad: aircraft.alpha,
  });
});

<WingVapor ref={vapor} airframe={airframe} air={air} />;

updateFlight only works the physics out again when a value changed, so it is cheap to call every frame. updateAir rewrites the condensation table, a few hundred moist adiabats: fine every frame, but not free. Write it when the altitude or the humidity actually change.

A prop and the ref drive the same thing. When a prop changes, it wins again.

From the load factor

A flight model usually knows the g, not the angle of attack. angle_of_attack_for_load inverts the lift curve for it:

import { moist_air } from "@aeronautic/core";
import { angle_of_attack_for_load } from "@aeronautic/wing-vapor/physics";

const air = moist_air(altitude_m, relative_humidity, temperature_offset_k);
const airspeed_m_s = mach * air.soundMPerS;

const alpha = angle_of_attack_for_load(
  vapor.airframe, // its mass included
  g,
  airspeed_m_s,
  air,
);

vapor.updateFlight({ airspeedMPerS: airspeed_m_s, angleOfAttackRad: alpha });

Past the most the wing can lift, it returns the angle of that maximum: the wing is stalled, and pulls no harder.

If the camera follows the flight path, pitch the model up by that angle: the nose sits above the path by the angle of attack.

Rolls and sideslips

A roll loads the wing going down harder than the one coming up, and a sideslip loads one wing of a swept aircraft more than the other. Each tip's vortex, each leading-edge vortex and each half of the sheet takes its own side's share, so the downgoing wing in a rolling pull fogs first and trails longest.

  • rollRateRadPerS: left out, the vapor measures it from how the group it follows turns, frame by frame. Give it when your flight model knows it.
  • sideslipRad: positive blows from +Z in the canonical frame (x aft, y up, z along the span). None by default.

The trails follow the flight

A vortex stays in the air it was shed into while the aircraft flies on, so a trail is the tip's own path through the air: a curve in a turn, a corkscrew in a roll. The vapor records the attitude of what it follows every frame and integrates the airspeed along it. You don't have to move the aircraft through the world for the trails to stream: a model held still in a showcase trails the same as one flown through a game's world.

Reading what it worked out

vapor.state is everything the vapor is drawn from:

const { air, flight, visible } = vapor.state;

air.dewPointK; // and the density, the pressure, the speed of sound…
flight.mach;
flight.loadFactor;
flight.lift_coefficient;
flight.breakdown; // how much of the leading-edge vortex is whole, 1 to 0
visible; // whether anything can fog at all

On this page