Shaped nozzles
Oval, flat, square or any outline you draw: plumes from nozzles that aren't round.
Not every nozzle is round. A fighter's petals often close to an oval. A flat vectoring nozzle is a slot several times wider than it is tall. A lobed mixer has a scalloped rim. Four params set the exit's outline, and a hook takes any other shape.
The outline params
<Afterburner
preset="afterburner"
params={{
nozzleRadiusM: 0.27,
nozzleAspect: 1.4, // width over height
nozzleSquareness: 2, // 2 an ellipse, higher squarer
nozzleRoll: 0, // radians about the plume's axis
nozzleOutlineLength: 1, // in potential core lengths
}}
/>| param | default | what it does |
|---|---|---|
nozzleAspect | 1 | Width over height. 1 is round (or square), 1.4 a fighter's oval, 3+ a slot. |
nozzleSquareness | 2 | The superellipse's exponent: 1 a diamond, 2 an ellipse, 4 a rounded rectangle. |
nozzleRoll | 0 | Turns the outline about the plume's axis, right-handed about the gas's way. |
nozzleOutlineLength | 1 | How far downstream the shape lasts before the jet is round. 0 is round. |
The outline is a superellipse, |u/a|ⁿ + |v/b|ⁿ = 1. That one family covers
almost every real nozzle, from ellipses to rounded rectangles, and every
plume in a batch may have its own, in the same instanced draw.
Size stays nozzleRadiusM
For a shaped exit, nozzleRadiusM is the radius of the round exit of the
same area. The mass flow is the same, so the core length, the shock spacing
and every other length the jet works out stay right. Every preset's radius
also fits any outline. To match a model's oval exit of width w and height
h:
const nozzle_radius_m = Math.sqrt((w / 2) * (h / 2)); // an ellipse's
const nozzle_aspect = w / h;For other squarenesses, nozzle_outline_fit(aspect, squareness) gives the
outline's size against that radius. Its areaScale times nozzleRadiusM is
the outline's mean semi-axis, √(ab).
Which way is wide
The width runs along the nozzle frame's x and the height along its y,
with the plume streaming down +Z. A plume aimed with direction is turned the
shortest way from +Z, which keeps its y as near up as it can. So a wide nozzle
aimed level comes out wide from side to side. Anything else is a
nozzleRoll: a quarter turn, Math.PI / 2, stands it on its side.
On a model, the plume rides the anchor's frame. Aim the anchor (see Placement) so the exit's long side lies along the axis you want, or roll the outline to fit.
Mixing round
A jet from a shaped exit doesn't keep its shape. Its long sides and corners
shear faster than the rest, so within a few diameters it's round, the way any
jet is downstream. The plume relaxes from the outline at the lip to a circle
over nozzleOutlineLength potential core lengths. At the default of 1, the
shape is all but gone where the core closes. Raise it for a slot that should
read flat for longer. At 0, the plume is round from the lip.
Any outline
The outline hook replaces the superellipse with any shape. You give its
radius in every direction across the plume, in nozzle radii, as TSL:
import { type AfterburnerHooks } from "@aeronautic/afterburner";
import { AfterburnerBatch } from "@aeronautic/afterburner/react";
import { atan, cos } from "three/tsl";
// Eight lobes, 15% deep: r(θ) = 1 + 0.15 cos 8θ
const LOBED: AfterburnerHooks = {
outline: {
radius: (direction) =>
cos(atan(direction.y, direction.x).mul(8)).mul(0.15).add(1),
reach: 1.15,
},
};
<AfterburnerBatch hooks={LOBED}>
<Afterburner preset="afterburner" />
</AfterburnerBatch>;radius(direction, frame): how far out the outline is indirection, in nozzle radii.directionis unit length in the outline's own axes: x along its width, y up its height, afternozzleRoll.frameis the plume frame at the sample, so the outline may change downstream too.reach: the mostradiusever returns. The bounds around the plume are widened by it. Anything past it is cut off, and a reach much larger than the outline costs steps.
Keep the outline's area near a unit circle's, π, and nozzleRadiusM keeps
its meaning. A hook is the batch's, so every plume in that batch takes the
outline. Each still turns by its own nozzleRoll and relaxes over its own
nozzleOutlineLength, and nozzleAspect and nozzleSquareness no
longer apply. Give other shapes their own <AfterburnerBatch>, as the lobed
exit above has.
Make hooks stable: a module constant or useMemo. A new object recompiles
the material. See Shader hooks.
Cost
A round nozzle costs what it did. An outline adds two pows per sample, and
its reach widens the hull, so a slot of aspect 3 marches somewhat more empty
space beside its narrow sides. A hook costs whatever its nodes cost: an atan
and a cos are cheap, but a texture lookup per sample is not.
In your own material
The field reads the outline from jet.outline (PlumeOutlineNodes) through
plume_frame, so a custom material built from the
TSL gets it by filling that in, and
plume_outline is the measure on its own. A custom geometry writes the
afterburner_outline and afterburner_outline_fit attributes, the second
from nozzle_outline_fit. See the material helpers.