Documentation
    Preparing search index...

    Interface ForceField

    §27's ForceField, transcribed member-for-member.

    const wind: ForceField = {
    sample(_position, _velocity, time, out) {
    const target = out ?? new Vector3();
    return target.set(Math.sin(time) * 40, 0, 0);
    },
    };

    Structurally identical to @fourjs/particles' ParticleForceField, so a field written for either pillar works in both (module header). Contract for an implementation, restated from §27 and the particle transcription so that a reader of this package alone has all of it:

    • Do not mutate position or velocity. They are the caller's scratch copies of the body's live state.
    • Write into out when it is supplied and return it — the system always supplies one, and doing so is what keeps the step allocation-free (§7b). Returning a different vector is legal (the caller reads the return value, never out), but that vector must stay valid until the next call.
    • out is not zeroed by the caller. Write every component.
    • Be a pure function of its arguments (§33). No clock — the absolute simulation time arrives as time, in seconds. No Math.random — take a SeededRandom at construction if the field is procedural.

    A field that returns a non-finite component poisons the body it is sampled at; nothing here checks, for the same reason nothing checks in the particle path — the check would run per body per field per step, and §85's place for it is the field's own constructor.

    ForceField.sampleAll is the optional §27 batch fast path (R-34's particle spelling, reused here so a field written for either pillar keeps one identity). ForceFieldSystem calls it when a field offers it; ForceField.sample stays the required §27 entry point.

    interface ForceField {
        sample(
            position: Vector3,
            velocity: Vector3,
            time: number,
            out?: Vector3,
        ): Vector3;
        sampleAll?(
            positions: ArrayLike<number>,
            velocities: ArrayLike<number>,
            count: number,
            time: number,
            out: Float64Array,
        ): void;
        sampleTorque?(
            position: Vector3,
            velocity: Vector3,
            angularVelocity: Vector3,
            time: number,
            out?: Vector3,
        ): Vector3;
    }
    Index
    • The contribution at position, for a body moving at velocity, at absolute simulation time time (seconds). Whether the vector is read as newtons or as m/s² is declared by the caller — see ForceFieldUnits.

      Parameters

      Returns Vector3

    • Optional fast path: the same contribution as ForceField.sample, for count bodies at once, added into out (stride-3 xyz).

      This is @fourjs/particles' ParticleForceField.sampleAll transcribed member-for-member so every built-in particle field is a batched ForceField with no adapter. Contract, restated from that transcription:

      • Add, do not assign. The caller owns out and may hand over a zeroed buffer or a running sum; overwriting it deletes whatever was already there.
      • out is binary64. The scalar path accumulates in JavaScript numbers; a binary32 accumulator would make a batched step differ from a scalar one in the last bits, and §33 does not permit that.
      • Be bit-identical to sample. Same arithmetic, same order, same special cases. Positions and velocities are xyz at stride 3, valid for [0, 3 · count). Read, never write.
      • Same purity rules as sample: no clock, no Math.random.

      A field that omits this is not penalised: the system falls back to ForceField.sample for that field alone, in the same registration order, with the same result.

      Parameters

      • positions: ArrayLike<number>
      • velocities: ArrayLike<number>
      • count: number
      • time: number
      • out: Float64Array

      Returns void

    • Optional angular channel (PH-8 remainder, 2026-09-06). Torque in newton-metres at the same sample point ForceField.sample uses.

      §27's sample stays one linear vector so a ParticleForceField remains assignable without an adapter. Torque is a second, optional method:

      • Always N·m. ForceFieldUnits scale the linear sample only. An "acceleration" reading would need the inertia tensor, which a field does not have and which is not a scalar.
      • Same purity and out rules as ForceField.sample. Do not mutate position, velocity, or angularVelocity.
      • A field that omits this contributes no torque.

      Parameters

      Returns Vector3