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

@drawcall/glts

v0.10.0

Published

Like glTF, but for procedural Three.js assets.

Readme

GLTS

Like glTF, but for procedural Three.js assets.

GLTS loads trusted, exportless TypeScript scripts into native Three.js scenes. Scripts build scenes with ordinary Three.js APIs and can compose other GLTS assets explicitly through a contextual loader.

import * as THREE from "three"
import { onDispose, scene } from "@drawcall/glts"

const geometry = new THREE.IcosahedronGeometry()
const material = new THREE.MeshStandardMaterial({ color: "orange" })
scene.add(new THREE.Mesh(geometry, material))

onDispose(() => {
  geometry.dispose()
  material.dispose()
})

GLTS is executable code, not a sandbox or serialization format. Only load code you trust.

Viewer

Run pnpm dev to open the example viewer. It accepts a trusted, self-contained .glts file by drag and drop or file picker and includes a bundled multi-file vintage racecar.

Load a scene

Install GLTS next to the application's Three.js dependency:

pnpm add @drawcall/glts three

The current physics integration supports Three.js r185 (>=0.185.0 <0.186.0).

import { GLTSLoader } from "@drawcall/glts"
import * as THREE from "three"

const loadingManager = new THREE.LoadingManager()
const loader = new GLTSLoader(loadingManager)
const tree = await loader.loadAsync("/assets/tree.glts")

scene.add(tree) // A loaded scene is also an ordinary Object3D subtree.
tree.update(deltaSeconds)

await tree.reload()
tree.dispose()
loader.dispose()

The result is a stable THREE.Scene with seven additions:

  • url: canonical source URL;
  • capabilities: read-only dependency declarations, including physics;
  • reload(): executes the latest source while preserving scene identity;
  • update(delta): dispatches registered frame callbacks;
  • dispose(): releases this scene and its nested GLTS scenes;
  • defaultCamera: optional authored camera used by GLTSRenderer when the host omits one;
  • rendering: renderer settings used by GLTSRenderer when this scene is the render root.

Native scene properties such as background, environment, fog, and overrideMaterial work directly. clone() remains the ordinary Three.js clone operation and produces an unmanaged snapshot.

load() has the familiar Three.js callback shape. load(), loadAsync(), loadInstancesAsync(), and reload() accept strings or URL objects. Track aggregate progress with loadingManager.onProgress; per-load progress callbacks are rejected because nested GLTS and resource requests form one manager-owned loading graph.

Compose scripts

Scripts do not import other .glts files statically. They use the contextual gltsLoader instance:

import { gltsLoader, scene } from "@drawcall/glts"

const branch = await gltsLoader.loadAsync(
  new URL("./branch.glts", import.meta.url),
)
scene.add(branch)

Every nested result must be added to the current script's scene before the script finishes. Unattached results fail the parent load and are disposed.

The host creates an execution-scoped recursive loader backed by the same module cache, lifecycle, live-node registry, and host loading manager. The scope makes nested ownership unambiguous when scripts execute concurrently. Explicit URL construction keeps relative resolution correct; .href is not needed by GLTS methods.

Script context

The supported runtime imports are:

import {
  gltsLoader,
  instanceCount,
  loadingManager,
  onDispose,
  onFrame,
  onMatrixUpdateAt,
  isPreview,
  scene,
} from "@drawcall/glts"

GLTS rewrites these imports for each execution. scene, counts, flags, and callbacks never leak between concurrently executing scripts.

Scripts may use top-level await. Work started during script evaluation through Three.js loaders using the contextual loadingManager is included in that script's completion, including work started without awaiting its promise. Notifications are forwarded to the host manager without mixing failures between concurrent scripts.

Host URL modifiers are respected. Extension handlers must be registered with the contextual manager and constructed with it; host-bound handler instances cannot be reused safely across concurrent script scopes.

Frame updates and cleanup

let elapsed = 0
onFrame((delta) => {
  elapsed += delta
  mesh.rotation.y = elapsed
})

onDispose(() => {
  texture.dispose()
  geometry.dispose()
  material.dispose()
})

Updating a parent scene also updates managed descendants. GLTS calls disposal callbacks in reverse registration order. It does not infer resource ownership.

Root presentation and preview staging

Scene properties and rendering describe this scene when it is the render root. Use isPreview to gate staging that exists only for standalone inspection:

import * as THREE from "three"
import { isPreview, onDispose, scene } from "@drawcall/glts"
import { UnrealBloomPass } from "three/addons/postprocessing/UnrealBloomPass.js"

scene.defaultCamera = new THREE.PerspectiveCamera(35, 1, 0.1, 100)
scene.defaultCamera.position.set(4, 3, 6)
scene.defaultCamera.lookAt(0, 1, 0)

if (isPreview) {
  const floor = new THREE.Mesh(
    new THREE.CircleGeometry(20),
    new THREE.MeshStandardMaterial({ color: "#333333" }),
  )
  floor.rotation.x = -Math.PI / 2
  floor.receiveShadow = true
  const light = new THREE.DirectionalLight("white", 3)
  light.position.set(4, 6, 3)
  light.castShadow = true
  scene.background = new THREE.Color("#171b2b")
  scene.fog = new THREE.Fog("#171b2b", 8, 40)
  scene.add(light, light.target, floor)

  scene.rendering.shadows = true
  scene.rendering.toneMapping = THREE.ACESFilmicToneMapping
  scene.rendering.toneMappingExposure = 1.1
  scene.rendering.effects.push(({ height, width }) =>
    new UnrealBloomPass(new THREE.Vector2(width, height), 0.3, 0.4, 0.85),
  )

  onDispose(() => {
    light.dispose()
    floor.geometry.dispose()
    floor.material.dispose()
  })
}

Set castShadow on intrinsic meshes outside the preview block when they should cast in any lighting setup; enabling renderer shadows alone creates none.

Use new GLTSLoader(manager, { isPreview: true }) in a preview application. Only loads initiated through that host loader can receive isPreview === true. Every contextual nested load receives false at every depth, and keeps that value across reloads, so standalone presentation cannot leak into composition. An asset may recommend a camera through scene.defaultCamera. It need not be added to the scene graph unless it depends on an authored parent. The preview application decides whether to use it or supply another camera; GLTS never searches the scene hierarchy for one.

scene.rendering describes the renderer state needed to present this root:

  • shadows
  • localClippingEnabled
  • toneMapping
  • toneMappingExposure
  • effects, an array of factories returning Three.js post-processing Pass instances compatible with WebGLRenderer.setEffects()

Factories receive the selected camera, loaded scene, and initial drawing-buffer width and height. The selected camera is the explicit host camera, or scene.defaultCamera when the host omits one. Use the supplied scene and camera for effects that need them. Do not add RenderPass or OutputPass: GLTS renders the beauty pass and final output itself. The adapter owns and disposes the returned passes. The optional third argument to render() is forwarded to passes as their frame delta.

The physical renderer, WebGL context, canvas, output color space, shadow-map filtering, viewport, camera selection and projection, controls, and frame loop remain application-owned. A preview host opts into the rendering profile explicitly:

import { GLTSLoader, GLTSRenderer } from "@drawcall/glts"
import * as THREE from "three"

const renderer = new THREE.WebGLRenderer({
  ...GLTSRenderer.parameters,
  canvas,
})
const gltsRenderer = new GLTSRenderer(renderer)
const loader = new GLTSLoader(manager, { isPreview: true })
const root = await loader.loadAsync("/asset.glts")

root.update(delta)
gltsRenderer.render(root, undefined, delta)

root.dispose()
gltsRenderer.dispose()

Omitting the camera uses root.defaultCamera; rendering fails if neither is available. Passing a camera as the second argument always overrides the authored default. defaultCamera is qualified because render() accepts a competing camera argument; native scene properties have no competing argument.

One GLTSRenderer can render different root assets sequentially across frames while reusing the same WebGL context. It applies each root's settings for that call, then restores the host renderer's settings. It exclusively manages renderer.setEffects() for its lifetime: create only one adapter per physical renderer and do not call setEffects() elsewhere. Construct the WebGL renderer with GLTSRenderer.parameters when effects are used; it selects the HDR output buffer required by Three.js. GLTS cannot inspect how an existing renderer was constructed. V1 effects and HDR tone mapping require the default framebuffer, a full viewport, and disabled scissor testing; render-target and split-viewport pipelines remain application-owned.

Changing the camera rebuilds that root's passes. Call release(scene) before disposing an effect-bearing scene if the adapter remains alive; GLTSRenderer.dispose() releases every cached pass. XR is outside V1. If the host reads renderer.info, set info.autoReset = false and reset it once per frame because post-processing performs nested render calls.

For embedding, renderer.render(asset, camera) and custom pipelines still work because the result is a native scene. They intentionally ignore asset.rendering; only the root scene passed to GLTSRenderer.render() owns presentation. A nested asset's background, environment, fog, override material, default camera, and rendering profile do not override its parent.

V1 intentionally targets Three.js WebGL post-processing. PMNDRS composers, Three.js WebGPU/TSL, TypeGPU, and other custom pipelines can consume the native scene directly, but are not translated into scene.rendering.

Load instances

The promoted instancing API mirrors Three.js naming:

const rocks = await loader.loadInstancesAsync("/assets/rock.glts", 1000)

const matrix = new THREE.Matrix4()
for (let index = 0; index < rocks.count; index += 1) {
  matrix.makeTranslation(index * 2, 0, 0)
  rocks.setMatrixAt(index, matrix)
}

scene.add(rocks)

GLTSInstances is a distinct addable scene with immutable count, getMatrixAt(), setMatrixAt(), reload(), update(), and dispose().

Ordinary mesh scripts are automatically converted to THREE.InstancedMesh objects. A script can implement native instancing when it needs special handling:

import * as THREE from "three"
import {
  instanceCount,
  onDispose,
  onMatrixUpdateAt,
  scene,
} from "@drawcall/glts"

const mesh = new THREE.InstancedMesh(geometry, material, instanceCount)
scene.add(mesh)

onMatrixUpdateAt((index, matrix) => {
  mesh.setMatrixAt(index, matrix)
  mesh.instanceMatrix.needsUpdate = true
})

onDispose(() => {
  mesh.dispose()
  geometry.dispose()
  material.dispose()
})

Registering onMatrixUpdateAt selects native instancing. Current matrices are replayed at registration, and later outer setMatrixAt() calls are forwarded immediately. The script executes once, whether GLTS uses native or automatic instancing. Reload preserves count and matrices and may switch implementation.

Automatic instancing rejects animated, composed, skinned, already-instanced, and other non-mesh renderable hierarchies. Such scripts must implement native instancing instead of receiving an approximate result.

Reload and errors

Reload builds the replacement before changing the live node. Fetch, compilation, import, or execution failure leaves the current content mounted. The node's identity and application-owned transform survive successful reload; authored root metadata, default camera, and render state refresh with the new revision.

loader.reload(url) clears cached source and updates every undisposed node loaded from that URL. If there are none, it does no fetching or scene work; the next load fetches current source. Pending loads finish before invalidation. URLs use the same resolution as loading, and each resolved request URL has its own cache entry. The application decides when a preview is inactive and disposes its nodes.

Uncached GLTS source requests use HTTP no-cache, including the first load, so the server validates freshness before source enters the loader's cache. Subsequent loads share the cached promise until invalidation. External module caching is unchanged.

Failures are GLTSError values with url, phase, and cause. GLTS reports source and resource requests through the supplied Three.js loading manager.

Script restrictions

A .glts file:

  • exports nothing;
  • imports contextual values by name from @drawcall/glts;
  • cannot import or construct GLTSLoader;
  • loads nested .glts files with gltsLoader;
  • cannot call gltsLoader.reload(); reload is a host/live-node operation;
  • may import three, Three.js addons, and browser-compatible npm packages;
  • may not use dynamic imports, import attributes, or local helper modules.

three always resolves to the host application's exact namespace. Three.js addons and bare npm packages are fetched through ESM.sh with three redirected to that host namespace.

The page must allow CORS access to sources and dependencies and permit blob: ES modules in its CSP.

Workspace

pnpm install
pnpm dev
pnpm check
pnpm test:e2e

Package tests live in packages/glts/test, beside src; browser tests live in examples/viewer/e2e. Both are included in strict TypeScript checks.

Browser tests start their own server. If port 5173 is occupied, use GLTS_E2E_PORT=5187 pnpm test:e2e to select another port.

Agent skill

Install the GLTS authoring guidance for your coding agent:

npx skills add drawcall-ai/glts --skill glts

Physics and USD

Physics authoring lives in the separate @drawcall/physics library:

import * as THREE from "three"
import { scene } from "@drawcall/glts"
import { RigidBody } from "@drawcall/physics"

const body = new RigidBody({ mass: 2 })
body.add(new THREE.Mesh(
  new THREE.BoxGeometry(),
  new THREE.MeshStandardMaterial(),
))
scene.add(body)

The host sets up a world before loading a physical asset:

import { setupWorld } from "@drawcall/physics-rapier"
import { GLTSLoader } from "@drawcall/glts"

const world = await setupWorld()
const loader = new GLTSLoader(manager)
const asset = await loader.loadAsync("/body.glts")
// Each frame: world.update(delta), asset.update(delta), then render.

For authoring/export without simulation, create an AuthoringWorld from @drawcall/physics and pass { physicsWorld: world } as the loader's second argument. The same option selects a particular simulation world when a host uses several worlds.

GLTS lazily loads the authoring library. Each execution gets constructors bound to its selected world; objects retain the host library's instanceof behavior. The world is selected when the first physics dependency resolves, inherited by nested loads, and retained across reloads and asynchronous/frame callbacks. Inside an asset, getDefaultWorld() returns that execution's world. Host world setup functions should be called outside asset scripts.

CDN helpers importing physics get the same execution binding. Physics-dependent module instances are isolated per execution; fetched source and physics-free module instances remain cached. Missing world setup throws rather than silently creating a world.

Bodies and joints stay outside the simulation while an asset or its nested loads are being constructed. A successful load registers the complete hierarchy; a reload keeps the previous physics active until replacement commits. Attach and configure the returned scene synchronously after await loader.loadAsync(...), before the next world.update(), including update(0). Physics captures scale during preparation, including the host's final parent transform. Do not step the world inside the asset's construction script. Objects created later by onFrame() register normally for the next preparation boundary.

A scene owns every physics object constructed by its execution, including objects not added to the scene tree and objects created with .clone() or clone(root). Disposal unregisters them automatically. Failed loads/reloads clean up their new objects while retaining the current asset. The host still owns the world and must dispose it when finished. Configure body mass/type and joint frames/limits through constructors. Set velocity, material, and connected-body contact through setVelocity, setMaterial, and setCollideConnected. The authoring skill covers physics asset construction and animation. body.getColliders() returns the actual explicit or automatically generated collider objects. Bodies and joints expose .validate(); joint.getFrame(index, matrix) writes a body-local joint frame into a Three.js Matrix4.

Use ordinary Three.js groups to organize mechanisms. The clone(root) helper from @drawcall/physics clones the hierarchy and reconnects internal joint references to the cloned bodies, preserving references to bodies outside the hierarchy. Individual body/joint .clone() and .copy() methods retain their ordinary object-level roles; clones belong to the same world.

A managed asset exposes read-only asset.capabilities.physics. This declares a runtime import dependency, not the presence of a particular body: even a side-effect-only physics import declares the capability. Transitive package imports and owned nested GLTS loads contribute to it. A nested reload updates the parent's effective declaration; a failed reload preserves the current revision. Type-only imports do not declare runtime physics. Manually inserted physics objects from host code do not declare a script dependency; use the physics exporter directly for such ordinary Three.js scenes.

import { GLTSUSDExporter } from "@drawcall/glts"

const exporter = new GLTSUSDExporter()
const usdz = await exporter.parseAsync(asset)

The exporter selects Three.js's native USDZ exporter for visual assets. A physics declaration lazily loads @drawcall/physics-usd, which exports the visuals and resolved physical shapes, bodies, materials, and joints. Export does not initialize a simulation backend. Export an authored/reset scene, not a scene currently displaying simulated poses. Import of the supported USD Physics subset is available separately through PhysicsUSDLoader from @drawcall/physics-usd.

Simulation is provided by @drawcall/physics-rapier; it is not required for loading or exporting an asset. Physics instancing is currently rejected: load independent scenes until per-instance body ownership is supported.

Runnable physics example

See examples/physics for a simulated ragdoll with automatic colliders, ball joints, limited elbows and knees, reset, and USDZ export.