Taos API Reference
    Preparing search index...

    Type Alias ModifierNode

    ModifierNode:
        | { type: "gravity"; strength: number }
        | { type: "drag"; coefficient: number }
        | { type: "force"; direction: [number, number, number]; strength: number }
        | { type: "swirl_force"; speed: number; strength: number }
        | { type: "vortex"; strength: number }
        | {
            type: "curl_noise";
            scale: number;
            strength: number;
            timeScale: number;
            octaves?: number;
        }
        | {
            type: "turbulence";
            scale: number;
            strength: number;
            timeScale: number;
            octaves?: number;
        }
        | {
            type: "wind";
            direction: [number, number, number];
            strength: number;
            turbulence: number;
            scale: number;
            timeScale: number;
        }
        | {
            type: "point_attractor";
            position: [number, number, number];
            space: ModifierSpace;
            strength: number;
            radius: number;
        }
        | {
            type: "radial_force";
            center: [number, number, number];
            space: ModifierSpace;
            strength: number;
        }
        | { type: "speed_limit"; max: number }
        | { type: "size_random"; min: number; max: number }
        | { type: "size_over_lifetime"; start: number; end: number }
        | {
            type: "size_by_speed";
            speedMin: number;
            speedMax: number;
            sizeMin: number;
            sizeMax: number;
        }
        | { type: "rotation_rate"; rate: number }
        | { type: "rotation_over_lifetime"; start: number; end: number }
        | {
            type: "color_over_lifetime";
            startColor: [number, number, number, number];
            endColor: [number, number, number, number];
        }
        | { type: "alpha_over_lifetime"; start: number; end: number }
        | {
            type: "box_color";
            min: [number, number, number];
            max: [number, number, number];
            space: ModifierSpace;
        }
        | { type: "velocity_color" }
        | {
            type: "bounds_kill";
            min: [number, number, number];
            max: [number, number, number];
            space: ModifierSpace;
        }
        | {
            type: "plane_collision";
            center: [number, number, number];
            normal: [number, number, number];
            space: ModifierSpace;
            bounce: number;
            friction: number;
            kill: boolean;
        }
        | {
            type: "sphere_collision";
            center: [number, number, number];
            radius: number;
            space: ModifierSpace;
            bounce: number;
            friction: number;
            kill: boolean;
            dynamic?: boolean;
        }
        | {
            type: "block_collision";
            bounce: number;
            friction: number;
            kill: boolean;
            stick?: boolean;
        }
        | { type: "sdf_attractor"; strength: number; offset: number }
        | {
            type: "sdf_collision";
            bounce: number;
            friction: number;
            kill: boolean;
        }
        | {
            type: "boids";
            perception: number;
            separation: number;
            cohesionWeight: number;
            alignWeight: number;
            separationWeight: number;
            maxSpeed: number;
            minSpeed: number;
            worldHalf: number;
            edgeMargin: number;
            edgeForce: number;
            verticalHalf?: number;
        }
        | {
            type: "predator_prey";
            perception: number;
            separation: number;
            preySpeed: number;
            predatorSpeed: number;
            fleeWeight: number;
            chaseWeight: number;
            cohesionWeight: number;
            alignWeight: number;
            separationWeight: number;
            worldHalf: number;
            edgeMargin: number;
            edgeForce: number;
        }
        | {
            type: "lotka_volterra";
            perception: number;
            killRadius: number;
            separation: number;
            preySpeed: number;
            predatorSpeed: number;
            chaseWeight: number;
            fleeWeight: number;
            cohesionWeight: number;
            alignWeight: number;
            separationWeight: number;
            drain: number;
            eatGain: number;
            killProb: number;
            startEnergy: number;
            maxEnergy: number;
            preyBirthRate: number;
            predReproEnergy: number;
            predBirthRate: number;
            predReproCost: number;
            predOffspringEnergy: number;
            baseSize: number;
            worldHalf: number;
            edgeMargin: number;
            edgeForce: number;
        }
        | {
            type: "link_springs";
            restLength: number;
            stiffness: number;
            repulsion: number;
            repelRadius: number;
            damping: number;
            maxSpeed: number;
            worldHalf: number;
            edgeMargin: number;
            edgeForce: number;
        }
        | {
            type: "schelling";
            radius: number;
            threshold: number;
            step: number;
            worldHalf: number;
        }
        | { type: "expression"; graph: ExpressionGraph
        | ExprNodeGraph }
        | { type: "sdf_stick"; threshold?: number }

    Per-frame behavior applied to live particles in the update compute pass.

    Type Declaration

    • { type: "gravity"; strength: number }

      Constant downward acceleration applied to velocity.y.

    • { type: "drag"; coefficient: number }

      Exponential velocity damping: v -= v * coefficient * dt each step.

    • { type: "force"; direction: [number, number, number]; strength: number }

      Constant directional acceleration along direction scaled by strength.

    • { type: "swirl_force"; speed: number; strength: number }

      Time-rotating XZ acceleration; speed sets rotation rate, strength the magnitude.

    • { type: "vortex"; strength: number }

      Solid-body rotation in XZ around the emitter — tangential force grows with radius.

    • {
          type: "curl_noise";
          scale: number;
          strength: number;
          timeScale: number;
          octaves?: number;
      }

      Divergence-free turbulent flow (FBM curl noise). scale is spatial frequency, timeScale drifts the field, octaves (≥1) sums frequencies for richer detail.

    • {
          type: "turbulence";
          scale: number;
          strength: number;
          timeScale: number;
          octaves?: number;
      }

      Plain vector noise — cheaper than curl_noise but not divergence-free (particles may collect into sinks). Useful for jitter/chaos.

    • {
          type: "wind";
          direction: [number, number, number];
          strength: number;
          turbulence: number;
          scale: number;
          timeScale: number;
      }

      Directional force whose strength is modulated by low-frequency noise, giving gusts. turbulence in [0,1] mixes constant vs. noise-modulated strength.

    • {
          type: "point_attractor";
          position: [number, number, number];
          space: ModifierSpace;
          strength: number;
          radius: number;
      }

      Pulls particles toward position with linear falloff to zero at radius. space chooses between absolute world coords and an offset from the emitter origin.

    • {
          type: "radial_force";
          center: [number, number, number];
          space: ModifierSpace;
          strength: number;
      }

      Pushes particles outward from center (negative strength = pull inward). space chooses between absolute world coords and an offset from the emitter origin.

    • { type: "speed_limit"; max: number }

      Clamps |velocity| to max; useful to cap runaway accumulation from forces.

    • { type: "size_random"; min: number; max: number }

      Per-particle stable random size in [min, max] — same slot always gets the same size.

    • { type: "size_over_lifetime"; start: number; end: number }

      Interpolates particle size from start → end over the particle's lifetime.

    • {
          type: "size_by_speed";
          speedMin: number;
          speedMax: number;
          sizeMin: number;
          sizeMax: number;
      }

      Maps |velocity| in [speedMin, speedMax] to size in [sizeMin, sizeMax]; out-of-range clamps.

    • { type: "rotation_rate"; rate: number }

      Constant angular velocity (radians/sec) added to sprite rotation each frame.

    • { type: "rotation_over_lifetime"; start: number; end: number }

      Interpolates sprite rotation (radians) from start → end over lifetime.

    • {
          type: "color_over_lifetime";
          startColor: [number, number, number, number];
          endColor: [number, number, number, number];
      }

      Interpolates RGBA color from startColor → endColor over lifetime.

    • { type: "alpha_over_lifetime"; start: number; end: number }

      Interpolates only the alpha channel over lifetime — cheaper than a full color curve.

    • {
          type: "box_color";
          min: [number, number, number];
          max: [number, number, number];
          space: ModifierSpace;
      }

      While the particle is inside the box [min, max], sets its RGB to the normalized position within the box (each axis mapped to 0..1). Alpha is preserved. Particles outside the box are left unchanged. space chooses between absolute world coords and an offset from the emitter origin.

    • { type: "velocity_color" }

      Sets RGB to normalize(abs(velocity)) — each channel is the fraction of the velocity's magnitude carried by that axis. Alpha is preserved. Particles with zero velocity are left unchanged.

    • {
          type: "bounds_kill";
          min: [number, number, number];
          max: [number, number, number];
          space: ModifierSpace;
      }

      Kills particles that leave the axis-aligned bounding box [min, max]. space chooses between absolute world coords and an offset from the emitter origin (the box is translated as a whole; its axes stay world-aligned).

    • {
          type: "plane_collision";
          center: [number, number, number];
          normal: [number, number, number];
          space: ModifierSpace;
          bounce: number;
          friction: number;
          kill: boolean;
      }

      Half-space collider: a plane through center with the given normal. Particles on the negative-normal side are pushed back to the surface. If kill is true the particle dies on contact and bounce/friction are ignored; otherwise bounce in [0,1] is the coefficient of restitution along the normal and friction in [0,1] damps the tangential component. space chooses how center is interpreted; normal is always a direction.

    • {
          type: "sphere_collision";
          center: [number, number, number];
          radius: number;
          space: ModifierSpace;
          bounce: number;
          friction: number;
          kill: boolean;
          dynamic?: boolean;
      }

      Spherical collider: particles inside the sphere are pushed out and reflected. bounce / friction / kill semantics match plane_collision. space chooses how center is interpreted.

      • type: "sphere_collision"
      • center: [number, number, number]
      • radius: number
      • space: ModifierSpace
      • bounce: number
      • friction: number
      • kill: boolean
      • Optionaldynamic?: boolean

        When true the collider's center + radius are read from a per-frame uniform (set via ParticlePass.setSphereCollider) instead of being baked in as constants, so the sphere can move at runtime to track e.g. a physics body. center / radius here seed the initial value; radius 0 disables it.

    • {
          type: "block_collision";
          bounce: number;
          friction: number;
          kill: boolean;
          stick?: boolean;
      }

      Heightmap collider. Three contact modes, checked in order:

      • kill: true — particle dies on contact (fires on_death + splits).
      • stick: true — particle freezes in place for the rest of its life: velocity is stored as a unit-length "impact direction" and the PARTICLE_FLAG_STUCK flag is set. The update loop then skips movement/force modifiers and the sprite renderer reorients the quad to face that direction (snow settling on terrain).
      • default — bounces off the surface treating it as a (0,1,0) ground plane; bounce/friction follow plane_collision semantics.

      Requires the particle pass to bind a heightmap (handled automatically when this modifier is present). kill takes precedence over stick.

    • { type: "sdf_attractor"; strength: number; offset: number }

      Pulls particles toward the surface of the bound ParticleGraphConfig.sdf. Uses the SDF gradient as the attraction direction and the signed distance as a per-step pull magnitude, so particles converge onto the surface and slow down as they near it. offset shifts the target surface outward (positive) or inward (negative) in world units — useful for "halo" hovering or for negative SDF effects.

    • { type: "sdf_collision"; bounce: number; friction: number; kill: boolean }

      SDF collider for the bound ParticleGraphConfig.sdf. Particles whose sampled distance is negative (inside the surface) are pushed back along the gradient with the same bounce / friction / kill semantics as plane_collision.

    • {
          type: "boids";
          perception: number;
          separation: number;
          cohesionWeight: number;
          alignWeight: number;
          separationWeight: number;
          maxSpeed: number;
          minSpeed: number;
          worldHalf: number;
          edgeMargin: number;
          edgeForce: number;
          verticalHalf?: number;
      }

      Flocking (Boids): steers each particle by its neighbors within perception world units — cohesion toward the local center of mass, alignment toward the average neighbor velocity, and separation away from neighbors closer than separation. Requires ParticleGraphConfig.spatialHash (the neighbor query runs over that grid). By default it flocks in the XZ plane (y forces zeroed). Set verticalHalf to make it a 3-D swarm: the steering keeps its vertical component and a soft edge force confines the flock to y ∈ [-verticalHalf, verticalHalf] as well. A soft edge force keeps the flock inside [-worldHalf, worldHalf] and speed is clamped to [minSpeed, maxSpeed]. The agent-modeling layer's first agent-to-agent (not field-mediated) behavior.

      • type: "boids"
      • perception: number
      • separation: number
      • cohesionWeight: number
      • alignWeight: number
      • separationWeight: number
      • maxSpeed: number
      • minSpeed: number
      • worldHalf: number
      • edgeMargin: number
      • edgeForce: number
      • OptionalverticalHalf?: number

        When set, flock in 3-D within this vertical half-extent (meters) instead of flat in the XZ plane.

    • {
          type: "predator_prey";
          perception: number;
          separation: number;
          preySpeed: number;
          predatorSpeed: number;
          fleeWeight: number;
          chaseWeight: number;
          cohesionWeight: number;
          alignWeight: number;
          separationWeight: number;
          worldHalf: number;
          edgeMargin: number;
          edgeForce: number;
      }

      Two-species predator/prey flocking. Like boids but neighbor steering depends on each agent's species, read from color.a (> 0.7 = predator). Predators chase the local center of mass of nearby PREY and space out from other predators; prey flee the local predator center, herd with other prey (cohesion + alignment), and separate. Requires ParticleGraphConfig.spatialHash. Works in the XZ plane; soft edge force + per-species speed clamp. The first multi-species agent model.

    • {
          type: "lotka_volterra";
          perception: number;
          killRadius: number;
          separation: number;
          preySpeed: number;
          predatorSpeed: number;
          chaseWeight: number;
          fleeWeight: number;
          cohesionWeight: number;
          alignWeight: number;
          separationWeight: number;
          drain: number;
          eatGain: number;
          killProb: number;
          startEnergy: number;
          maxEnergy: number;
          preyBirthRate: number;
          predReproEnergy: number;
          predBirthRate: number;
          predReproCost: number;
          predOffspringEnergy: number;
          baseSize: number;
          worldHalf: number;
          edgeMargin: number;
          edgeForce: number;
      }

      Lotka-Volterra predator/prey with TRUE population dynamics (births + deaths via the GPU free-list — requires ParticleGraphConfig.reproduction and .spatialHash). Species in color.a (> 0.7 = predator); predators store energy in rotation. Predators chase prey, lose drain/s, gain eatGain per prey within killRadius (energy capped at maxEnergy); they DIE when energy hits 0, and REPRODUCE (a numerical response — offspring with predOffspringEnergy, costing predReproCost) when energy ≥ predReproEnergy. Prey flee + herd, DIE when eaten, and REPRODUCE at preyBirthRate/s (logistic — births fail once the buffer/free-list is full = carrying capacity). The boom-bust of those two growing/shrinking populations gives the classic oscillation. Velocity-based; XZ plane. Seed the initial population via ParticlePass.seedAgents.

    • {
          type: "link_springs";
          restLength: number;
          stiffness: number;
          repulsion: number;
          repelRadius: number;
          damping: number;
          maxSpeed: number;
          worldHalf: number;
          edgeMargin: number;
          edgeForce: number;
      }

      Spring forces along an agent's links (the AgentScript "links" primitive): each agent is pulled toward each of its linked agents at restLength (Hooke, scaled by stiffness), while repulsion pushes it away from nearby agents within repelRadius (via the spatial hash) so the graph spreads. Velocity is damped each step (damping) so the layout settles — a GPU force-directed graph layout. Requires both ParticleGraphConfig.links (the per-agent link slots) and .spatialHash. Works in the XZ plane; soft edge force + speed clamp.

    • {
          type: "schelling";
          radius: number;
          threshold: number;
          step: number;
          worldHalf: number;
      }

      Schelling segregation. Two types flagged in color.a (> 0.7 = type B). An agent is "happy" if the fraction of same-type neighbors within radius is at least threshold (isolated agents count as happy); UNHAPPY agents take a random walk of length step each frame (torus-wrapped). Like types thus accrete into stable single-type clusters — segregation emerges even at modest thresholds. Position-based (velocity unused). Requires ParticleGraphConfig.spatialHash. Works in the XZ plane.

    • { type: "expression"; graph: ExpressionGraph | ExprNodeGraph }

      Runs an expression graph as a modifier. Accepts either the compact AST form (ExpressionGraph from particle_expr.ts) or the flat node-graph form (ExprNodeGraph from particle_expr_graph.ts); the two are disambiguated structurally ('actions' in graph vs 'nodes' in graph) and both lower to the same WGSL. Use the AST form when hand-coding for brevity; use the node-graph form when constructing from the visual editor or when callers want to preserve the explicit wiring for visualization. Order in the surrounding modifiers list matters: an expression placed after gravity sees the updated velocity, etc.

    • { type: "sdf_stick"; threshold?: number }

      Sticks particles to the bound ParticleGraphConfig.sdf surface on contact. When the particle's signed distance falls to threshold or below (default 0 = strictly inside), the particle is snapped onto the surface along the SDF gradient and its velocity is zeroed. Run after sdf_attractor and integration-affecting modifiers (gravity, drag, noise) in the modifier list so the snap wins for the frame; with velocity pinned at zero, the next integration step keeps the position at the surface, so stuck particles stay stuck for the rest of their lifetime. Other modifiers (color/size over lifetime) continue to apply. Use a small positive threshold (e.g. 0.005) to grab fast-moving particles that would otherwise tunnel past the surface in a single step.