Documentation
    Preparing search index...

    Interface ParticleForceField

    §27's ForceField, as particles consume it. See the module note for the naming and the units.

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

    Contract for an implementation:

    • Do not mutate position or velocity. They are the caller's scratch copies of the particle's live state; writing to them corrupts the step.
    • Write into out when it is supplied and return it — the emitter always supplies one, and doing so is what keeps the hot path allocation-free (§7b, plan D7). 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.
    interface ParticleForceField {
        gpuField?: ParticleGpuRadialField;
        sample(
            position: Vector3,
            velocity: Vector3,
            time: number,
            out?: Vector3,
        ): Vector3;
        sampleAll?(
            positions: Float32Array,
            velocities: Float32Array,
            count: number,
            time: number,
            out: Float64Array,
        ): void;
    }
    Index

    When present, a simulation: "gpu" emitter may apply this field on the device. Absent on every built-in except radialField.

    • Acceleration contributed at position, for a particle moving at velocity, at absolute simulation time time (seconds).

      Parameters

      Returns Vector3

    • Optional fast path: the same contribution as ParticleForceField.sample, for count particles at once, added into out (R-34, §27/§112).

      sampleAll(positions, velocities, count, time, out) {
      for (let i = 0; i < count; i += 1) {
      const base = i * 3;
      out[base] += -c * velocities[base];
      out[base + 1] += -c * velocities[base + 1];
      out[base + 2] += -c * velocities[base + 2];
      }
      }

      Measured attribution in benchmarks/results/particles-100k.json: the integrator alone costs 1.31 ms per 100 000 particles, and each field adds ~5.15 ms — three fields consume the whole 16.67 ms fixed-step budget. Almost none of that is the field's arithmetic. It is one megamorphic sample() call, two Vector3.sets, and three property reads, per particle per field. A field that loops internally pays the call once per step instead of once per particle, and JIT-inlines its own monomorphic body.

      • Add, do not assign. out arrives carrying the emitter's gravity and every earlier field's contribution; overwriting it deletes them. (This is the one place this entry point departs from §27's sample, which produces a value. Producing here would force the emitter to allocate a second buffer and make a third pass over 3 × count floats per field, which is most of the win.)
      • out is a Float64Array, not Float32Array. The scalar path accumulates in JavaScript numbers, i.e. binary64; a binary32 accumulator would round after every field and make a batched run differ from a scalar one in the last bits. §33 does not permit "fast, and also slightly different".
      • Be bit-identical to sample. Same arithmetic, same order of operations, same special cases. A field that ships both owes a test that pins them against each other — every built-in field has one.
      • positions and velocities are the pool's live arrays, xyz at stride 3, valid for indices [0, 3 · count). Read, never write.
      • Same purity rules as sample: no clock, no Math.random.

      A field that does not implement this is not penalised: the emitter falls back to sample for that field alone, in the same declaration order, with the same result.

      Parameters

      • positions: Float32Array
      • velocities: Float32Array
      • count: number
      • time: number
      • out: Float64Array

      Returns void