@pixi/3d
    Preparing search index...

    Interface Event3DFields

    The 3D fields a View3D adds to the pointer and wheel events it passes into its scene

    They sit on pixi's own event object, so 2D listeners that run after the View passes the event on, such as the View's plain listeners and bubbling ones on its 2D ancestors, can read them too.

    • Copy out anything you keep. Pixi reuses event objects, so later events overwrite hit3D and its vectors, as they do global.
    • The View's on* handlers and *capture listeners run before it passes the event on, so there the fields can be null. Read them in a plain listener, view3d.on('pointertap', …).
    • In a 3D node's listeners, except on globalpointermove, globalmousemove and globaltouchmove, composedPath() returns the 3D chain, [view3d.root, …, target], in an array the View can empty after the event. Copy it to keep it.
    • On events that never passed through a View3D, the fields read null. The exception is a 2D node's pointerover and mouseover when the pointer moves straight off the View onto it: target3D still names the 3D node it left.
    mesh.hitVolume = 'geometry'; // per-triangle hits, so `normal` isn't zero
    mesh.on('pointertap', (e) => {
    if (!e.hit3D) return;
    spawnDecal(e.hit3D.point, e.hit3D.normal); // reading inside the handler is fine
    lastHit.copyFrom(e.hit3D.point); // keeping the value needs a copy
    });
    interface Event3DFields {
        currentTarget3D: Container3D | null;
        hit3D: RaycastHit3D | null;
        target3D: Container3D | null;
    }

    Hierarchy (View Summary)

    Index
    currentTarget3D: Container3D | null

    The 3D node whose listeners are running, or null outside the 3D dispatch

    The 3D counterpart of currentTarget: while target3D stays on the event's target, this moves from node to node, so an ancestor's listener reads the ancestor. The View's own listeners read null.

    // One listener on the group tells which node it fired on
    group.on('pointerdown', (e) => {
    // e.target3D is the node hit, or the group when that node isn't interactive
    console.log(e.currentTarget3D === group); // true
    });
    hit3D: RaycastHit3D | null

    What the pointer is over: the hit View3D#raycast would return for event.global, or null when the ray hits nothing

    It ignores which node is being told, so a pointerupoutside on the pressed node reports where the pointer was released, such as on a drop target, and a pointerout reports the node the pointer moved onto. It is null on the pointerout as the pointer leaves the View and on a release outside the View.

    ground.on('pointertap', (e) => {
    if (e.hit3D) marker.position.copyFrom(e.hit3D.point); // drop a marker where the ray landed
    });

    // Only per-triangle volumes carry surface data
    wall.hitVolume = 'geometry';
    wall.on('pointertap', (e) => {
    if (e.hit3D && e.hit3D.normal.y > 0.9) console.log('floor, not wall');
    });
    target3D: Container3D | null

    The 3D node this event is about, or null when it belongs to the View, as on a click on empty space

    The View's own listeners see the View as event.target, while target3D still names the node.

    • In a 3D node's *upoutside listeners it names the pressed node.
    • During pointerenter and pointerleave, which don't bubble, it names the node being told.
    • When the ray strikes a non-interactive ('auto' or 'passive') node, it names that node's nearest interactive ancestor.
    // One View-level listener instead of a listener per mesh
    view3d.on('pointertap', (e) => {
    if (e.target3D) select(e.target3D);
    else clearSelection();
    });