@pixi/3d
    Preparing search index...

    Interface ParticleSystem3DOptions<B>

    Constructor options for ParticleSystem3D, on top of the ParticleContainer3DOptions

    An omitted field takes its value from ParticleSystem3D.defaultOptions.

    const sparks = new ParticleSystem3D({
    capacity: 40, // emitRate × the longest life, 40 × 0.8, rounded up
    blendMode: 'add',
    emitRate: 40,
    life: [0.4, 0.8],
    speed: [2, 4],
    alphaEnd: 0,
    gravity: 9.8,
    });
    interface ParticleSystem3DOptions<
        B extends readonly ShaderBit[] = readonly ShaderBit[],
    > {
        alpha?: ParticleSpread;
        alphaCutoff?: number;
        alphaEnd?: ParticleSpread;
        alphaMode?: AlphaMode;
        angle?: PointData3D;
        autoPlay?: boolean;
        autoUpdate?: boolean;
        billboard?: ParticleBillboardMode;
        blendMode?: ParticleBlendMode;
        boundsVolume?: Box;
        bursts?: readonly ParticleBurst[];
        capacity?: number;
        children?: Container3D[];
        color?: ColorSource;
        colorEnd?: ColorSource;
        createRandom?: () => () => number;
        cursor?: string & {} | Cursor | null;
        drag?: number;
        emitRate?: number;
        eventMode?: EventMode;
        extraBits?: B;
        frameMode?: ParticleFrameMode;
        frameRate?: number;
        gravity?: number | PointData3D;
        hitVolume?: HitVolume3DInput;
        interactive?: boolean;
        interactiveChildren?: boolean;
        label?: string | null;
        life?: ParticleSpread;
        onclick?: Pointer3DEventHandler | null;
        oncontextmenu?: Pointer3DEventHandler | null;
        onglobalmousemove?: Pointer3DEventHandler | null;
        onglobalpointermove?: Pointer3DEventHandler | null;
        onglobaltouchmove?: Pointer3DEventHandler | null;
        onmousedown?: Pointer3DEventHandler | null;
        onmouseenter?: Pointer3DEventHandler | null;
        onmouseleave?: Pointer3DEventHandler | null;
        onmousemove?: Pointer3DEventHandler | null;
        onmouseout?: Pointer3DEventHandler | null;
        onmouseover?: Pointer3DEventHandler | null;
        onmouseup?: Pointer3DEventHandler | null;
        onmouseupoutside?: Pointer3DEventHandler | null;
        onpointercancel?: Pointer3DEventHandler | null;
        onpointerdown?: Pointer3DEventHandler | null;
        onpointerenter?: Pointer3DEventHandler | null;
        onpointerleave?: Pointer3DEventHandler | null;
        onpointermove?: Pointer3DEventHandler | null;
        onpointerout?: Pointer3DEventHandler | null;
        onpointerover?: Pointer3DEventHandler | null;
        onpointertap?: Pointer3DEventHandler | null;
        onpointerup?: Pointer3DEventHandler | null;
        onpointerupoutside?: Pointer3DEventHandler | null;
        onrightclick?: Pointer3DEventHandler | null;
        onrightdown?: Pointer3DEventHandler | null;
        onrightup?: Pointer3DEventHandler | null;
        onrightupoutside?: Pointer3DEventHandler | null;
        ontap?: Pointer3DEventHandler | null;
        ontouchcancel?: Pointer3DEventHandler | null;
        ontouchend?: Pointer3DEventHandler | null;
        ontouchendoutside?: Pointer3DEventHandler | null;
        ontouchmove?: Pointer3DEventHandler | null;
        ontouchstart?: Pointer3DEventHandler | null;
        onUpdate?: ((dt: number, system: ParticleSystem3D<B>) => void) | null;
        onwheel?: Wheel3DEventHandler | null;
        origin?: number | PointData3D;
        parent?: Container3D;
        position?: PointData3D;
        quaternion?: QuaternionData;
        rotation?: PointData3D;
        scale?: number | PointData3D;
        shape?: EmitterShape;
        size?: ParticleSpread;
        sizeEnd?: ParticleSpread;
        space?: ParticleSpace;
        speed?: ParticleSpread;
        spin?: ParticleSpread;
        startRotation?: ParticleSpread;
        texture?:
            | Texture<TextureSource<any>>
            | readonly Texture<TextureSource<any>>[];
        visible?: boolean;
        x?: number;
        y?: number;
        z?: number;
    }

    Type Parameters

    Hierarchy (View Summary)

    Index

    Starting opacity

    1
    
    const haze = new ParticleSystem3D({ alpha: [0.2, 0.4] });
    
    alphaCutoff?: number

    Alpha below which a texel is cut away under alphaMode: 'mask'

    0.5
    
    const leaves = new ParticleSystem3D({ texture: leafTexture, alphaMode: 'mask', alphaCutoff: 0.3 });
    
    alphaEnd?: ParticleSpread

    End-of-life opacity; set it to 0 for the standard fade-out. Omit to hold the spawn opacity

    const smoke = new ParticleSystem3D({ alpha: 0.5, alphaEnd: 0 });
    
    alphaMode?: AlphaMode

    How particle transparency works

    • 'blend': blends over the scene with blendMode. Particles draw in slot order, not sorted by depth, which shows under 'normal' blending.
    • 'mask': hard-edged cutouts that write depth, so they overlap correctly in any order; suits leaves and debris.
    • 'opaque': solid quads, the cheapest; only for textures with no transparent texels.
    'blend'
    
    const leaves = new ParticleSystem3D({ texture: leafTexture, alphaMode: 'mask' });
    
    angle?: PointData3D

    Local rotation as Euler angles in degrees: turns around Z, then Y, then X, each about the parent's axes

    { x: 0, y: 0, z: 0 }
    
    const turned = new Container3D({ angle: { x: 0, y: 90, z: 0 } });
    
    autoPlay?: boolean

    Whether the system is playing from the start

    true
    
    const confetti = new ParticleSystem3D({
    autoPlay: false,
    emitRate: 0,
    bursts: [{ time: 0, count: 300 }],
    });

    winBanner.on('shown', () => confetti.play());
    autoUpdate?: boolean

    Whether update runs from Ticker.shared every frame; ParticleSystem3D.autoUpdate says when to turn it off

    true
    
    const steam = new ParticleSystem3D({ autoUpdate: false });

    app.ticker.add((ticker: Ticker) => steam.update(ticker));

    How each particle faces the camera; see ParticleBillboardMode

    'spherical'
    
    const fire = new ParticleSystem3D({ billboard: 'cylindrical' });
    
    blendMode?: ParticleBlendMode

    How particles blend over the scene; see ParticleBlendMode

    'normal'
    
    const sparks = new ParticleSystem3D({ blendMode: 'add' });
    
    boundsVolume?: Box

    Fixed local-space box used in place of this node's subtree when measuring bounds; see Container3D.boundsVolume

    const swarm = new Container3D({ boundsVolume: new Box(new Vector3(-10, 0, -10), new Vector3(10, 5, 10)) });
    
    bursts?: readonly ParticleBurst[]

    Bursts to fire on the system's burst timeline, which starts at 0 and advances only while the system is playing

    The list is copied at construction, so adding or removing a burst later does nothing. An edit to a burst's count or repeatEvery applies the next time it fires; an edit to its time applies on the next gotoAndPlay or gotoAndStop.

    const explosion = new ParticleSystem3D({ emitRate: 0, bursts: [{ time: 0, count: 150 }] });
    
    capacity?: number

    How many particles the system can hold, fixed for its lifetime

    Every slot costs 4 vertices whether or not a particle is in it, so size it to the peak you need.

    1000
    
    const puff = new ParticleSystem3D({ capacity: 64 });
    
    children?: Container3D[]

    Children to add, in order

    const car = new Container3D({ children: [body, frontWheels, rearWheels] });
    
    color?: ColorSource

    Starting tint, in display (sRGB) space

    A colour's own alpha is ignored; alpha sets opacity.

    0xffffff
    
    const sparks = new ParticleSystem3D({ color: 'orange' });
    
    colorEnd?: ColorSource

    End-of-life tint. Omit to hold the spawn tint

    const fire = new ParticleSystem3D({ color: 0xffdd55, colorEnd: 0xff2200 });
    
    createRandom?: () => () => number

    Makes the random stream spawns draw from, so an effect can replay the same way

    It's called at construction and on every gotoAndPlay and gotoAndStop, so with a seeded generator gotoAndPlay(0) replays the effect. A seek to a later time doesn't replay what the first run drew from there, but two seeks to the same time, followed by the same calls, draw the same particles.

    Omitted, each stream gets a random seed, so no two runs match. () => Math.random works too, but allocates on every draw.

    const campfire = new ParticleSystem3D({ createRandom: () => seededRandom('campfire') });

    campfire.gotoAndPlay(0); // the same sparks as the first run
    cursor?: string & {} | Cursor | null

    CSS cursor shown while this node is the pointer's target

    Only the target's own cursor counts, as in 2D: a hit on a 'passive' child shows its interactive ancestor's cursor, so set it on the node with the listeners. null shows the View3D's own cursor. Any CSS cursor value works, url(...) included.

    null
    
    button.cursor = 'pointer';
    handle.cursor = 'grab';
    blade.cursor = 'url("crosshair.png") 8 8, crosshair'; // custom, with a hotspot
    drag?: number

    How fast velocity dies away

    Velocity shrinks by a factor of e^-drag each second, so with gravity a particle settles at a falling speed of gravity / drag.

    const puff = new ParticleSystem3D({ speed: 8, drag: 3 });
    
    emitRate?: number

    Particles emitted per second. Set to 0 for a burst-only or fully manual system

    10
    
    const drizzle = new ParticleSystem3D({ emitRate: 50 });
    
    eventMode?: EventMode

    How this node takes part in pointer events, as on a pixi.js Container

    • 'none': never picked, and neither is anything below it. The subtree costs nothing per pointer event.
    • 'passive' (default): fires no listeners and is never the target. Under an interactive ancestor it is still picked, and the hit goes to that ancestor with this node's surface data. So one 'static' group makes its whole subtree clickable, with the group as target.
    • 'auto': as 'passive', but ray-tested even with no interactive ancestor, which costs time and finds no target. Prefer 'passive'.
    • 'static': picked against its hitVolume, and fires listeners.
    • 'dynamic': as 'static', and the View3D also picks again while a mouse or pen is still, so a node that moves under it gets its enter and leave events.

    The renderer's eventMode option only sets the default for 2D containers, and the View warns once when it isn't 'passive'. Picking costs time for each node that takes part, so set the mode on the nodes and groups that need it.

    'passive'
    
    button.eventMode = 'static';   // a clickable mesh
    spinner.eventMode = 'dynamic'; // it moves under a still pointer
    panel.eventMode = 'none'; // decoration; skip it and its children

    // A whole model that answers as one target; its meshes need no setup
    robot.eventMode = 'static';
    extraBits?: B

    Extra shader bits composed into the particle material

    Each bit's attributes get space in every particle, writable with ParticleContainer3D.setParticle, and its material bindings become fields on the container's uniforms. The bits run after the base colour is sampled, so they see and can change albedo and alpha; getParticleBit lists the vertex-stage locals they can change.

    const particles = new ParticleContainer3D({ extraBits: [particleLifetimeBit, particleAlphaOverLifeBit] });
    
    frameMode?: ParticleFrameMode

    How a multi-frame texture picks frames; ParticleFrameMode lists the modes

    Ignored for a single texture. 'animate' and 'overLife' read the sheet as a grid of cells the size of the first frame, in order left to right then top to bottom from the top-left, and ignore where each frame actually sits. Use 'random' for a sheet that isn't laid out that way.

    'random'
    
    const flames = new ParticleSystem3D({ texture: flameFrames, frameMode: 'animate' });
    
    frameRate?: number

    Frames per second for frameMode: 'animate'

    12
    
    const flames = new ParticleSystem3D({ texture: flameFrames, frameMode: 'animate', frameRate: 24 });
    
    gravity?: number | PointData3D

    Constant acceleration, in units per second squared; a number is shorthand for that much downward pull

    It's in the system's own space, so in a space: 'local' system it turns and scales with the node.

    const fountain = new ParticleSystem3D({ speed: 5, gravity: 9.8 });
    const windblown = new ParticleSystem3D({ gravity: { x: 1.5, y: -2, z: 0 } });
    hitVolume?: HitVolume3DInput

    Shape the pointer ray is tested against when picking this node, like hitArea in 2D

    Setting it doesn't make the node interactive; eventMode does that. Left null, a Mesh3D uses its geometry's bounding box and a plain Container3D has no shape, so it only hears events from its children.

    A shape you set also clips the node's descendants, as hitArea does in 2D: a descendant only hits where the hit point is inside every such shape above it, whatever those nodes' eventMode. A clipped hit doesn't block what is behind it. A Geometry3D clips by its bounding box, an IHitVolume3D only through its optional containsPoint, and the default bounding box never clips. A ray that misses a Box or Sphere shape, or a Geometry3D's bounding box, skips the exact test of every descendant under it.

    null
    
    mesh.interactive = true;                  // picked by its bounding box
    mesh.hitVolume = 'geometry'; // picked per triangle
    mesh.hitVolume = new Sphere(center, 0.4); // a cheaper or tighter shape
    mesh.hitVolume = null; // back to the bounding box

    // Children of the porthole only pick inside its sphere
    porthole.hitVolume = new Sphere(new Vector3(), 1);

    HitVolume3DInput For what each accepted value resolves to.

    interactive?: boolean

    Shorthand for eventMode, as on a pixi.js Container

    true sets 'static' and false sets 'passive'. It reads true for 'static' and 'dynamic'. false doesn't stop picking: under an interactive ancestor, a 'passive' node is still picked on that ancestor's behalf. Set eventMode = 'none' to take a node and its subtree out of picking.

    mesh.interactive = true;  // eventMode = 'static'
    mesh.interactive = false; // eventMode = 'passive'
    interactiveChildren?: boolean

    Whether this node's descendants can be picked, as on a pixi.js Container

    false takes the whole subtree out of picking: descendants neither hit nor block what is behind them, and cost nothing per pointer event. The node itself still picks by its own eventMode and volume, so it can answer for the subtree.

    true
    
    menu.interactiveChildren = false; // nothing in the menu can be picked
    menu.interactiveChildren = true; // every button picks again, eventMode intact

    // One sphere answers for the whole robot: one ray test instead of one per mesh
    robot.eventMode = 'static';
    robot.hitVolume = new Sphere(new Vector3(), 2);
    robot.interactiveChildren = false;
    label?: string | null

    Name to find the node by; see Container3D#label

    null
    
    const turret = new Container3D({ label: 'turret' });
    

    Lifetime in seconds

    1
    
    const embers = new ParticleSystem3D({ life: [1, 2.5] });
    
    onclick?: Pointer3DEventHandler | null

    Property-based handler for the click event

    Fired when a mouse click on this node completes, with any button but the right one.

    null
    
    mesh.eventMode = 'static';
    mesh.onclick = (e) => {
    if (e.hit3D) marker.position.copyFrom(e.hit3D.point);
    };

    IFederatedContainer3D#onpointertap For the equivalent for every pointer type.

    oncontextmenu?: Pointer3DEventHandler | null

    Property-based handler for the contextmenu event

    Fired when the browser is about to open its context menu over this node; call preventDefault() to keep it closed.

    null
    
    mesh.oncontextmenu = (e) => {
    e.preventDefault();
    openMenuFor(mesh, e.global);
    };
    onglobalmousemove?: Pointer3DEventHandler | null

    Property-based handler for the globalmousemove event

    Fired on every mouse move, wherever the pointer is on the page.

    null
    
    mesh.onglobalmousemove = (e) => {
    if (dragging) dragTo(e.global);
    };

    IFederatedContainer3D#onglobalpointermove For the equivalent for every pointer type.

    onglobalpointermove?: Pointer3DEventHandler | null

    Property-based handler for the globalpointermove event

    Fired on every pointer move on the page, whether or not the pointer is over this node. The node still needs eventMode 'static' or 'dynamic' to hear it.

    null
    
    // Keep a drag going once the pointer runs off the dragged mesh
    mesh.onglobalpointermove = (e) => {
    const hit = dragging && view3d.screenToWorld(e.global, ground);
    if (hit) mesh.position.copyFrom(hit);
    };
    onglobaltouchmove?: Pointer3DEventHandler | null

    Property-based handler for the globaltouchmove event

    Fired on every touch move, wherever the touch is on the page.

    null
    
    mesh.onglobaltouchmove = (e) => {
    if (dragging) dragTo(e.global);
    };

    IFederatedContainer3D#onglobalpointermove For the equivalent for every pointer type.

    onmousedown?: Pointer3DEventHandler | null

    Property-based handler for the mousedown event

    Fired when a mouse button other than the right one goes down over this node.

    null
    
    mesh.onmousedown = (e) => {
    if (e.button === 1) startPan(e.global); // middle button
    };

    IFederatedContainer3D#onpointerdown For the equivalent for every pointer type.

    onmouseenter?: Pointer3DEventHandler | null

    Property-based handler for the mouseenter event

    Fired when the mouse enters this node. Doesn't bubble.

    null
    
    mesh.onmouseenter = () => (tooltip.visible = true);
    

    IFederatedContainer3D#onpointerenter For the equivalent for every pointer type.

    onmouseleave?: Pointer3DEventHandler | null

    Property-based handler for the mouseleave event

    Fired when the mouse leaves this node. Doesn't bubble.

    null
    
    mesh.onmouseleave = () => (tooltip.visible = false);
    

    IFederatedContainer3D#onpointerleave For the equivalent for every pointer type.

    onmousemove?: Pointer3DEventHandler | null

    Property-based handler for the mousemove event

    Fired while the mouse moves across this node.

    null
    
    mesh.onmousemove = (e) => tooltip.position.copyFrom(e.global);
    

    IFederatedContainer3D#onpointermove For the equivalent for every pointer type.

    onmouseout?: Pointer3DEventHandler | null

    Property-based handler for the mouseout event

    Fired when the mouse moves off this node. Bubbles, unlike mouseleave.

    null
    
    shelf.onmouseout = (e) => unhighlight(e.target3D);
    

    IFederatedContainer3D#onpointerout For the equivalent for every pointer type.

    onmouseover?: Pointer3DEventHandler | null

    Property-based handler for the mouseover event

    Fired when the mouse moves onto this node. Bubbles, unlike mouseenter.

    null
    
    // One handler on the shelf hears every item on it
    shelf.onmouseover = (e) => highlight(e.target3D);

    IFederatedContainer3D#onpointerover For the equivalent for every pointer type.

    onmouseup?: Pointer3DEventHandler | null

    Property-based handler for the mouseup event

    Fired when a mouse button other than the right one is released over this node.

    null
    
    mesh.onmouseup = (e) => {
    if (e.button === 1) endPan();
    };

    IFederatedContainer3D#onpointerup For the equivalent for every pointer type.

    onmouseupoutside?: Pointer3DEventHandler | null

    Property-based handler for the mouseupoutside event

    Fired when a mouse button that went down on this node is released anywhere else.

    null
    
    mesh.onmouseupoutside = () => endPan();
    

    IFederatedContainer3D#onpointerupoutside For the equivalent for every pointer type.

    onpointercancel?: Pointer3DEventHandler | null

    Property-based handler for the pointercancel event

    Never called: pixi delivers no pointer cancellation, in 2D either.

    null
    
    // End a drag on these two instead
    handle.onpointerup = endDrag;
    handle.onpointerupoutside = endDrag;
    onpointerdown?: Pointer3DEventHandler | null

    Property-based handler for the pointerdown event

    Fired when a pointer goes down on this node: mouse, touch or pen alike.

    null
    
    handle.eventMode = 'static';
    handle.onpointerdown = (e) => {
    if (!e.hit3D) return;
    dragOffset.copyFrom(e.hit3D.localPoint); // where on the handle it was grabbed
    dragging = true;
    };
    onpointerenter?: Pointer3DEventHandler | null

    Property-based handler for the pointerenter event

    Fired when a pointer enters this node. Doesn't bubble, so an ancestor hears it only when the pointer enters the ancestor itself.

    null
    
    button.eventMode = 'static';
    button.onpointerenter = () => button.scale.set(1.1);
    onpointerleave?: Pointer3DEventHandler | null

    Property-based handler for the pointerleave event

    Fired when a pointer leaves this node. Doesn't bubble.

    null
    
    button.onpointerleave = () => button.scale.set(1);
    
    onpointermove?: Pointer3DEventHandler | null

    Property-based handler for the pointermove event

    Fired while a pointer moves across this node.

    null
    
    // Trail the cursor across the surface it is over
    table.eventMode = 'static';
    table.onpointermove = (e) => {
    if (e.hit3D) cursorMarker.position.copyFrom(e.hit3D.point);
    };
    onpointerout?: Pointer3DEventHandler | null

    Property-based handler for the pointerout event

    Fired when a pointer moves off this node. Bubbles, unlike pointerleave.

    null
    
    shelf.onpointerout = (e) => unhighlight(e.target3D);
    
    onpointerover?: Pointer3DEventHandler | null

    Property-based handler for the pointerover event

    Fired when a pointer moves onto this node. Bubbles, unlike pointerenter.

    null
    
    // One handler on the shelf hears every item on it
    shelf.onpointerover = (e) => highlight(e.target3D);
    onpointertap?: Pointer3DEventHandler | null

    Property-based handler for the pointertap event

    Fired when a pointer completes a tap on this node, for any pointer type and any button, right clicks included.

    null
    
    // Works for mouse, touch and pen alike
    mesh.eventMode = 'static';
    mesh.onpointertap = (e) => select(e.target3D);
    onpointerup?: Pointer3DEventHandler | null

    Property-based handler for the pointerup event

    Fired when a pointer is released over this node.

    null
    
    handle.onpointerup = () => {
    dragging = false;
    };
    onpointerupoutside?: Pointer3DEventHandler | null

    Property-based handler for the pointerupoutside event

    Fired when a pointer that went down on this node is released anywhere else. The event's target3D is the node that was pressed, and hit3D is what the pointer was released over.

    null
    
    // The release that ends a drag usually happens off the mesh
    handle.onpointerupoutside = () => {
    dragging = false;
    };
    onrightclick?: Pointer3DEventHandler | null

    Property-based handler for the rightclick event

    Fired when a right click completes on this node.

    null
    
    mesh.onrightclick = () => openMenuFor(mesh);
    
    onrightdown?: Pointer3DEventHandler | null

    Property-based handler for the rightdown event

    Fired when the right mouse button goes down over this node.

    null
    
    mesh.onrightdown = (e) => startRotate(e.global);
    
    onrightup?: Pointer3DEventHandler | null

    Property-based handler for the rightup event

    Fired when the right mouse button is released over this node.

    null
    
    mesh.onrightup = endRotate;
    
    onrightupoutside?: Pointer3DEventHandler | null

    Property-based handler for the rightupoutside event

    Fired when the right mouse button that went down on this node is released anywhere else.

    null
    
    mesh.onrightupoutside = endRotate;
    
    ontap?: Pointer3DEventHandler | null

    Property-based handler for the tap event

    Fired when a touch completes a tap on this node. Chrome gives each touch a new pointerId, and detail only counts repeat taps by the same one, so it stays 1 there; time a double tap yourself.

    null
    
    let lastTap = 0;
    mesh.ontap = (e) => {
    if (e.timeStamp - lastTap < 300) focusOn(mesh); // double tap
    lastTap = e.timeStamp;
    };

    IFederatedContainer3D#onpointertap For the equivalent for every pointer type.

    ontouchcancel?: Pointer3DEventHandler | null

    Property-based handler for the touchcancel event

    Never called: pixi delivers no touch cancellation, in 2D either.

    null
    
    // End a drag on these two instead
    handle.ontouchend = endDrag;
    handle.ontouchendoutside = endDrag;
    ontouchend?: Pointer3DEventHandler | null

    Property-based handler for the touchend event

    Fired when a touch lifts off this node.

    null
    
    handle.ontouchend = endDrag;
    

    IFederatedContainer3D#onpointerup For the equivalent for every pointer type.

    ontouchendoutside?: Pointer3DEventHandler | null

    Property-based handler for the touchendoutside event

    Fired when a touch that started on this node lifts off anywhere else.

    null
    
    handle.ontouchendoutside = endDrag;
    

    IFederatedContainer3D#onpointerupoutside For the equivalent for every pointer type.

    ontouchmove?: Pointer3DEventHandler | null

    Property-based handler for the touchmove event

    Fired while a touch moves across this node.

    null
    
    canvasMesh.hitVolume = 'geometry'; // per triangle, so hits carry a uv
    canvasMesh.ontouchmove = (e) => {
    if (e.hit3D) paintAt(e.hit3D.uv);
    };

    IFederatedContainer3D#onpointermove For the equivalent for every pointer type.

    ontouchstart?: Pointer3DEventHandler | null

    Property-based handler for the touchstart event

    Fired when a touch lands on this node.

    null
    
    handle.ontouchstart = (e) => startDrag(e.pointerId);
    

    IFederatedContainer3D#onpointerdown For the equivalent for every pointer type.

    onUpdate?: ((dt: number, system: ParticleSystem3D<B>) => void) | null

    Called at the end of each step; see ParticleSystem3D.onUpdate

    const flicker = new ParticleSystem3D({
    onUpdate: (_dt, system) => {
    system.emitRate = 30 + Math.sin(system.time * 4) * 20;
    },
    });
    onwheel?: Wheel3DEventHandler | null

    Property-based handler for the wheel event

    Fired when the wheel turns while the pointer is over this node.

    null
    
    // Scroll to push the panel away from the camera
    panel.eventMode = 'static';
    panel.onwheel = (e) => {
    panel.position.z += e.deltaY * 0.01;
    };
    origin?: number | PointData3D

    Point in local space that rotation and scale happen around; see Container3D#origin

    { x: 0, y: 0, z: 0 }
    
    const hinge = new Container3D({ origin: { x: 0.5, y: 0, z: 0 } });
    
    parent?: Container3D

    Parent to add this node to, once everything else is applied

    const crate = new Container3D({ label: 'crate', parent: view.root });
    
    position?: PointData3D

    Local position

    { x: 0, y: 0, z: 0 }
    
    const lamp = new Container3D({ position: { x: 0, y: 3, z: 0 } });
    
    quaternion?: QuaternionData

    Local rotation as a unit quaternion

    { x: 0, y: 0, z: 0, w: 1 }
    
    const turned = new Container3D({ quaternion: { x: 0, y: Math.SQRT1_2, z: 0, w: Math.SQRT1_2 } });
    
    rotation?: PointData3D

    Local rotation as Euler angles in radians: turns around Z, then Y, then X, each about the parent's axes

    { x: 0, y: 0, z: 0 }
    
    const turned = new Container3D({ rotation: { x: 0, y: Math.PI / 2, z: 0 } });
    
    scale?: number | PointData3D

    Local scale; a number scales all three axes

    1
    
    const giant = new Container3D({ scale: 3 });
    
    shape?: EmitterShape

    Where particles start and which way they head

    new PointShape()

    const motes = new ParticleSystem3D({ shape: new SphereShape({ radius: 2 }) });
    

    Starting world-space diameter

    1
    
    const dust = new ParticleSystem3D({ size: [0.05, 0.15] });
    
    sizeEnd?: ParticleSpread

    End-of-life size. Omit to hold the spawn size

    const puffs = new ParticleSystem3D({ size: 0.2, sizeEnd: [1, 1.5] });
    

    Whether particle positions are in world space ('world') or relative to this node ('local')

    World-space particles are left behind as the emitter moves, which suits smoke and dust. Local-space particles move with the node, so a system on a moving object travels with it, at no extra cost. Particle size is a world-space diameter either way; the node's scale doesn't change it.

    'world'
    
    const exhaust = new ParticleSystem3D({ space: 'local' });
    ship.addChild(exhaust);

    Initial speed along the shape's direction, in units per second

    0
    
    const jet = new ParticleSystem3D({ speed: [6, 8] });
    

    Rotation rate in radians per second. Omit to leave particles unspinning

    const debris = new ParticleSystem3D({ spin: [-3, 3] });
    
    startRotation?: ParticleSpread

    Each particle's starting spin angle in radians, turning its quad in its own plane

    The rotation option turns the emitter instead.

    0
    
    const leaves = new ParticleSystem3D({ startRotation: [0, Math.PI * 2] });
    
    texture?: Texture<TextureSource<any>> | readonly Texture<TextureSource<any>>[]

    The sprite drawn for every particle

    Pass an array of frames for a flipbook. The frames must share one TextureSource, such as a sprite sheet.

    Texture.WHITE
    
    const sparks = new ParticleSystem3D({ texture: sparkTexture });
    const flames = new ParticleSystem3D({ texture: sheet.animations.flame, frameMode: 'animate' });
    visible?: boolean

    Whether this node and its descendants draw

    true
    
    const hiddenUntilSpawn = new Container3D({ visible: false });
    
    x?: number

    Local position on x; the same as position.x

    const left = new Container3D({ x: -2 });
    
    y?: number

    Local position on y; the same as position.y

    const shelf = new Container3D({ y: 1.2 });
    
    z?: number

    Local position on z; the same as position.z

    const back = new Container3D({ z: -5 });