aeronautic

Placing plumes

Position, rotation, direction, targets, and moving a nozzle from code.

The one rule

A plume leaves its nozzle's origin along the nozzle's local +Z. That is aft of a model that flies along -Z with +Y up, the convention the other packages share. Every way of placing one just decides where that origin is and which way +Z points.

In JSX

<Afterburner> takes every <group> prop. position is the exit plane's centre. Turn it with rotation, or point it with direction:

// On a model built facing +X: exit 7 m behind its origin, exhaust towards −X
<Afterburner position={[-7, 0, 0]} direction={[-1, 0, 0]} />

// The same, as a rotation: a quarter turn about Y takes +Z to −X
<Afterburner position={[-7, 0, 0]} rotation={[0, -Math.PI / 2, 0]} />

direction is any vector in the parent's frame, normalised for you. When both are given, it wins over rotation.

The group's scale scales the plume, look and all. Usually you want params={{ nozzleRadiusM }} instead: it rescales every length of the jet, the way a bigger engine would.

On a node you don't own

For a node of a loaded model, or anything else outside your JSX, pass it as target. The plume is portalled into it, and position, rotation and direction are then in its frame:

const { nodes } = useGLTF("/fighter.glb");

<Afterburner
  target={nodes.FX_Exhaust_L}
  position={[0, 0, 0.08]}
  direction={[0, 0, -1]}
/>;

target takes any Object3D: a mesh, a group, a bone. If it comes from a ref, keep it in state so the plume re-renders once it exists. See the tutorial.

on-a-model.tsx
Starting…

Moving it from code

Through the handle, without a render:

// Move and aim it, in the frame it sits in
engine.current?.setOffset({
  position: [0, 0.2, -4.2],
  direction: [0, -0.1, -1],
});

// Just aim it
engine.current?.setDirection([0, -0.1, -1]);

// Or write the group directly: it is a plain three.js Group
engine.current?.object?.position.set(0, 0.2, -4.2);

setOffset takes an AfterburnerOffset:

fieldtype
position[x, y, z] or any { x, y, z }
rotationan Euler, a Quaternion, [x, y, z] or [x, y, z, w]
direction[x, y, z] or any { x, y, z }; wins over rotation

Fields you leave out are kept.

Without React

A core nozzle follows an object's world matrix, at an optional offset in its frame. attach switches which object it follows:

const tail = batch.add({
  object: engine_node,
  offset: { position: [0, 0, -4.2], direction: [0, 0, -1] },
});

tail.setOffset({ position: [0, 0.1, -4.2] });
tail.attach(other_engine); // follow something else
tail.attach(null); // stop following, and hold where it is now

Detaching folds the last world pose into the nozzle's matrix, so the plume doesn't jump. More in Without React.

On this page