npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@khronosgroup/gltf-interactivity-engine

v1.0.0

Published

Engine of the Khronos Interactivity Graph Authoring Tool

Readme

KHR_interactivity Typescript Engine

This README provides an overview and instructions for connecting the BasicBehaveEngine to a frontend (e.g. DCC tool, renderer).

Table of Contents

Introduction

This engine implements the glTF KHR_interactivity extension. The KHR_interactivity extension allows you to add interactive features and behaviors to your 3D models, enhancing the user experience in 3D applications.

NPM package

  1. Add this project as a npm package to your project via
npm i @khronosgroup/gltf-interactivity-engine

Integration

Registering Pointer mappings

The KHR_interactivity spec allows for setting and getting properties of an object model, this logic of mapping an object path to a setter and getter is handled by the respective decorator (so a logging engine can set a path in a different way than a Babylon one to help with implementation specific mapping). To add your own pointer, simply add the logic for your getter and setter to the registerKnownPointers function in the respective Decorators.

Loading a graph

A graph can be loaded into the engine by calling:

loadBehaveGraph(behaveGraph: any, run = true)

behaveGraph is the JSON representation of the graph. run can be set to false, if the graph should not be executed automatically after loading.

The event queue can be pause and played by calling:

pauseEventQueue()
playEventQueue()

Be aware that you need to pause/play e.g. animations and hover in your renderer.

Resetting a graph

Resetting the engine needs to be done manually. An empty graph can be loaded and there are several clear functions for different purposes.

Additionally, you need to manually reset the animation state and the whole glTF state which was modified by pointer/set nodes.

Events

To sent custom events to the engine call:

dispatchCustomEvent(name: string, vals: any)

The name needs to be prefixed with KHR_INTERACTIVITY:. Vals is a JSON object containing all values needed for the event.

For implementation details, check out the KHR_interactivity specification.

KHR_selectability and KHR_hoverability

To implement KHR_selectability and KHR_hoverability the following function need to be added to the BasicBehaveEngine:

this.behaveEngine.getParentNodeIndex = (nodeIndex: number) => number | undefined => {
   // Return index of parent node. If no parent exists return undefined.
}

You can pass a selection event via:

select(selectedNodeIndex: number, controllerIndex: number, selectionPoint: [number, number, number] | undefined, selectionRayOrigin: [number, number, number] | undefined);

selectionPoint and selectionRayOrigin are optional.

You can pass a hover event via:

hoverOn(hoveredNodeIndex: number | undefined, controllerIndex: number);

hoveredNodeIndex can be undefined if nothing is hover over.

KHR_physics_rigid_bodies

To implement KHR_physics_rigid_bodies register the nodes using:

this.registerRigidBodyNodes();

You can pass a trigger enter and exit events via:

rigidBodyTriggerEntered(nodeIndex: number, colliderNodeIndex: number, motionNodeIndex: number | undefined);

rigidBodyTriggerExited(nodeIndex: number, colliderNodeIndex: number, motionNodeIndex: number | undefined);

nodeIndex describes the node index of the trigger, colliderNodeIndex describes the node index of the collider and motionNodeIndex describes the index of the motion which might be attached to the collider or one of it parents. This should be called with undefined, if no motion is found (in case of a static collider).

The following functions need to be implemented and need to overwrite their counterparts in the behave engine:

applyImpulseToRigidBody(nodeIndex: number, linearImpulse: [number, number, number], angularImpulse: [number, number, number])
applyPointImpulseToRigidBody(nodeIndex: number, impulse: [number, number, number], position: [number, number, number])
rayCastRigidBodies(rayStart: [number, number, number], rayEnd: [number, number, number], collisionFilterIndex: number): {hitNodeIndex: number, hitFraction: number | undefined, hitNormal: [number, number, number] | undefined}

this.behaveEngine.applyImpulseToRigidBody = this.applyImpulseToRigidBody;
this.behaveEngine.applyPointImpulseToRigidBody = this.applyPointImpulseToRigidBody;
this.behaveEngine.rayCastRigidBodies = this.rayCastRigidBodies;

If a ray cast result in a miss, {hitNodeIndex: -1} should be returned. The full interactivity node definitions and explanations can be read in the spec.

Debugging

The following functions of the BasicBehaveEngine can be used to perform debugging/logging etc.

this.behaveEngine.processNodeStarted = (behaveEngineNode: BehaveEngineNode) => void {
   
};
this.behaveEngine.processAddingNodeToQueue = (flow: IInteractivityFlow) => void {

};
this.behaveEngine.processExecutingNextNode = (flow: IInteractivityFlow) => void {

};

Important Notes About Tool Execution

  1. The tool passes around JSON for the execution graph. Be aware that the engine does not necessarily create deep copies of arrays or objects. Modifying these from outside the engine might create undefined/unexpected behavior. Make sure to not modify passed values or create deep copies.
  2. The engine expects matrices in 2D structure e.g. [[1.0, 0.0],[0.0, 1.0]]. This needs to be considered for e.g. registering worldMatrix or matrix

Development

Prerequisites

  1. Install Node.js. Please use an LTS version of Node.js.

Installation

  1. Install the required dependencies:

    npm install

Building

  1. Run

    npm run build

All files are build into the build folder. Source maps are generated but not published via npm.

Adding Nodes to BasicBehaveEngine

The provided Basic BehaveEngine uses a decorator approach, so you behave nodes will be defined only once and the Logging and Babylon decorators will decorate certain touch points in the execution logic with their specific functionality. To create a new node...

  1. add a new class in the nodes directory which extends BehaveEngineNode.
  2. add your REQUIRED_VALUES and/or REQUIRED_CONFIGURATIONS arrays which will be used to validate the node.
  3. In your constructor, run super() then set the instance name and finally run the needed validations (flows, values and configurations) for your node
  4. override processNode, if your node has custom named out flows (i.e. in branch we have true and false instead of out) use this.processFlow, if not you can either do this.processFlow on the default out flow or run super.processNode
  5. Finally, either in BasicBehaveEngine or in the decorator of your choice, call registerBehaveEngineNode with the namespace of your node and the node class name
  6. If you want to do some complex behaviors, like async listeners look at CustomEvent/Receive it has a good example of how async nodes post their subsequent flows to the execution queue instead of invoking synchronously and shows how to set up an event listener so it is not triggered by another node but an event.