Needle Engine

Needle Engine is a web-first 3D framework built on three.js for building games, configurators, AR/VR experiences, and interactive websites.

Built-in: Rapier Physics | WebXR (incl. iOS) | Multiplayer & VOIP | Blender & Unity Integration

Built on three.js and the glTF standard, Needle Engine delivers flexible, extensible web experiences with built-in collaboration and XR support. Use it standalone with npm or with powerful editor integrations for Unity and Blender.

Changelog | Documentation | Samples | Showcase | API Reference

npm install @needle-tools/engine

Try it now on StackBlitz | Getting Started Guide

import { Behaviour, serializable } from "@needle-tools/engine";

export class MyComponent extends Behaviour {

@serializable()
speed: number = 1;

start() {
console.log("Component started on:", this.gameObject.name);
}

update() {
this.gameObject.rotateY(this.context.time.deltaTime * this.speed);
}
}
import { Behaviour, Rigidbody, BoxCollider } from "@needle-tools/engine";

export class PhysicsBox extends Behaviour {

awake() {
// Add a physics body — Rapier is built in, no extra install needed
const rb = this.gameObject.addComponent(Rigidbody);
rb.useGravity = true;

// Add a collider
this.gameObject.addComponent(BoxCollider);
}
}
import { Behaviour, syncField } from "@needle-tools/engine";

export class SyncedCounter extends Behaviour {
// Automatically synced across all connected clients
@syncField()
count: number = 0;

onPointerClick() {
// Reassign to trigger sync (this is required for sync to work)
this.count = this.count + 1;
}
}
import { onStart, onUpdate } from "@needle-tools/engine";

onStart((context) => {
console.log("Engine started!");
});

onUpdate((context) => {
// Called every frame
});

WebXR & AR — immersive experiences on Android and iOS

  • WebXR support including WebXR on iOS
  • WebXRImageTracking — AR image targets with full tracking lifecycle
  • WebXRPlaneTracking — AR surface detection
  • Interactive QuickLook for AR on Vision Pro

Scene & Asset Management

  • SceneSwitcher — load different scenes / hierarchies by URL
  • AssetReference — runtime asset loading by URL
  • Multi-scene support with dynamic content loading

Physics & Interaction — Built-in Rapier physics engine

  • Rigidbody, BoxCollider, SphereCollider, MeshCollider — full physics simulation
  • DragControls — click-and-drag 3D objects with zero code
  • SpatialTrigger — proximity and enter-zone detection

Multiplayer & Networking — real-time collaboration out of the box

  • SyncedRoom + @syncField() — automatic state synchronization
  • Voip — built-in WebRTC voice chat
  • PlayerSync — player object sync on join/leave
  • SyncedCamera — camera sync for observer sessions
  • Low level networking events for full control

Rendering & Effects

  • Advanced PBR rendering with lightmap support
  • Post-processing (Bloom, DepthOfField, SSAO, ChromaticAberration, Tonemapping and more)
  • Progressive texture and mesh loading with automatic LOD generation

Animation & Media

  • Animation state machines and timeline animations
  • VideoPlayer — full video playback component
  • AudioSource — 3D spatial audio
  • Animate anything via KHR_animation_pointer

Framework Integration — works with React, Vue, Svelte, or vanilla JS/TS

See all features

Needle Engine works standalone with just npm — no editor required. For asset-heavy workflows, use our editor integrations:

Preview Example Description Links
Multiuser Cross device experience, Desktop, AR & VR (Sandbox) Real-time collaborative multiplayer sandbox experience with WebXR on Android and iOS
Image Tracking AR AR image tracking example (iOS and Android). See docs
Scrollytelling Bike Example Timeline Animation using ScrollFollow, ViewBox and FocusRect Project on Github
See-Through Walls See-Through component sample
Cursor Follow Cursor Follow sample
Animate Anything Interactive animation system Code on Stackblitzthree.js Example
Postprocessing Effects Custom magnifier effect with post-processing Code on Stackblitz
Unity ShaderGraph to MaterialX & mtlx materials Using @needle-tools/materialx
Camera Focus DIV 1 Responsive layout with camera focus Code on Stackblitz
Camera Focus DIV 2 Click-to-move camera focus example Code on Stackblitz
FastHDR Loading 10x faster than EXR, non-blocking, 95% less GPU memory Code on StackblitzLearn more
Scrollytelling Example Scroll, physics and cursor interaction: a playful 3D interactive scrollytelling website Included in Samples Package
AR Restaurant Interactive AR restaurant experience Code on Github
Custom Loading Overlay Wait for LODs with custom loading states Code on Stackblitz
React Shopping Cart E-commerce integration with React Code on Stackblitz

👋 More examples on samples.needle.tools, docs.needle.tools and in the Needle Engine Stackblitz Collection


Contact ✒️

🌵 NeedleGithubTwitterDiscordForumYoutube


Recent Changes

  • WebXRImageTracking.imageTracked event — invoked every frame an image is tracked, with typed access to the tracked object instance:
    imageTracking.imageTracked.addEventListener(evt => {
    console.log(evt.object, evt.image);
    });
  • ?stats URL parameter now also logs renderer info (DPR, window DPR, antialias, MSAA samples, drawing buffer resolution)

Added

  • Context.events — typed event bus for decoupled component communication. Known events get autocomplete; custom events can be typed at the call site:
    context.events.on("scene-content-changed", e => console.log(e.object));
    context.events.emit<{ pts: number }>("scored", { pts: 10 });
  • ContactShadows auto-refit — when autoFit is enabled, shadows automatically refit when scene content changes (e.g. SceneSwitcher load, DropListener asset loading)
  • Input.addEventListener now returns an unsubscribe function (works with autoCleanup):
    this.autoCleanup(this.context.input.addEventListener("pointerdown", (evt) => { ... }));
    
  • AnimationBuilder — low-level API for defining animation tracks with typed keyframes and tween shorthands. Used by AnimatorControllerBuilder and TimelineBuilder for inline .track() calls
  • TimelineBuilder typed track builder interfaces per track type (AnimationTrackBuilder, AudioTrackBuilder, etc.) with inline .track() support for keyframe animation
  • AnimatorControllerBuilder: inline .track() for defining animation directly on states, support for TrackDescriptor arrays as clip sources, simplified exitTime (replaces separate hasExitTime flag)
  • Physics raycast includeTriggers option — opt-in to hitting trigger/sensor colliders (skipped by default)

Changed

  • Timeline track classes renamed for consistency: AnimationTrackHandlerTimelineAnimationTrack, AudioTrackHandlerTimelineAudioTrack, ActivationTrackHandlerTimelineActivationTrack, ControlTrackHandlerTimelineControlTrack, MarkerTrackHandlerTimelineMarkerTrack, TrackHandlerTimelineTrackHandler
  • OrbitControls.fitCamera deprecated overload removed from type declarations (runtime still accepts Object3D for backwards compat — use fitCamera({ objects: [...] }) instead)

Fixed

  • OrbitControls: programmatic camera transitions (e.g. fitCamera) no longer interrupted continuously during an ongoing drag — only at interaction start
  • fitCamera with centerCamera: "y" producing incorrect camera elevation when camera was above the scene center
  • Animator: switching runtimeAnimatorController now properly disposes the previous controller
  • AnimatorController.dispose() guard against missing mixer
  • CursorFollow.snapToSurface incorrect property description removed
  • Vite 8 compatibility fixes
  • autoCleanup on Behaviour — register disposables or cleanup functions tied to the component lifecycle. Automatically cleaned up on disable or destroy depending on when registered:
    onEnable() {
    this.autoCleanup(on(window, "resize", () => { ... }));
    this.autoCleanup(this.context.connection.beginListen("my-event", () => { ... }));
    }
  • TimelineBuilder API for building timeline assets from code:
    const timeline = TimelineBuilder.create("MyTimeline")
    .animationTrack("Walk", animator).clip(walkClip, { duration: 2 })
    .activationTrack("FX", vfxObject).activate({ start: 1, duration: 0.5 })
    .build();
    director.playableAsset = timeline;
  • PlayableDirector: expose tracks and activationTracks getters, support runtime playableAsset assignment with automatic graph rebuild
  • PhysicsCollider: expose density property, add @validate decorator for automatic property updates at runtime
  • Timeline AudioTrack volume getter/setter
  • Networking: beginListen now returns an unsubscribe function (backwards compatible — stopListen still works):
    const unsub = this.context.connection.beginListen("my-event", (data) => { ... });
    unsub(); // or use this.autoCleanup(unsub)
  • Serialization: consolidated instantiate reference resolution into a unified system — fixes cloned timelines, EventLists, SignalReceivers, and deep component references not resolving correctly after instantiate()
  • Removed legacy AvatarLoader, AvatarBlink_Simple, AvatarEyeLook_Rotation, Avatar_Brain_LookAt, Avatar_MouthShapes, Avatar_MustacheShake components
  • @validate decorator wrapping __internalAwake multiple times when components were re-activated
  • Instantiated scenes with timeline SignalReceiver not resolving EventList object references
  • instantiate() not including cloned Object3D GUIDs in the guids map
  • Light component throwing on unsupported light types (now logs error without breaking deserialization)
  • Worker URLs producing incorrect paths for SPA deployments
  • Vite license and project identifier error handling for non-500 status codes
  • Rapier physics dispose() not clearing internal state (freed world could cause crashes)
  • AudioClip type with standalone playback control — use with @serializable(AudioClip) for direct audio clip references
  • AudioSource spatial blend support via dual-path audio graph for smooth 2D/3D crossfade
  • UI Text default static font from Needle CDN with absolute font URL support
  • DragControls EventList support
  • UI Text font URL resolution when loading GLBs from external hosts (e.g. CDN)
  • AudioSource spatial rolloff factor and play() reliability
  • Animation play() with { exclusive: false } not stopping already-running clips
  • XR lifecycle — spurious onLeaveXR calls for scripts that never entered XR
  • OrbitControls lookBounds lerp and distance-relative gizmo sizes
  • CursorFollow self-intersection and snapToSurface raycast direction
  • Vite makeFilesLocal and alias plugins now respect vite.config base
  • three-mesh-bvh worker failing to load in local dev server
  • VideoPlayer URL serializer tree-shaken out in code-only projects
  • ReflectionProbe not cleaning up overrides when removed