npm package: @linear_non/stellar-gl
Description: WebGL setup & helpers for Non-Linear Studio projects.
Latest version: 1.4.4

# stellar-gl

> `@linear_non/stellar-gl` — WebGL / Three.js layer for Non-Linear Studio projects.

A lightweight and modular WebGL layer built on top of Three.js, designed to integrate seamlessly with [`stellar-kit`](https://www.npmjs.com/package/@linear_non/stellar-kit). Supports full-page single-canvas setups and per-DOM-element modular GL components.

## Installation

```bash
npm install @linear_non/stellar-gl
```

Requires `three` and `@linear_non/stellar-kit`:

```bash
npm install three @linear_non/stellar-kit
```

> `@theatre/core` is bundled as a direct dependency — no additional install needed.  
> For the editor UI, install in devDependencies only: `npm install -D @theatre/studio`

---

## Package exports

```
@linear_non/stellar-gl/classes   → classes/index.js
@linear_non/stellar-gl/utils     → utils/index.js
@linear_non/stellar-gl/three     → libraries/three/index.js
@linear_non/stellar-gl/loaders   → loaders/index.js
@linear_non/stellar-gl/shaders/* → shaders/*
@linear_non/stellar-gl/decoders/* → decoders/*
```

---

## Classes

| Class          | Description                                                                                                                              |
| -------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `App`          | Main entry point. Wires stellar-kit events → world + canvas lifecycle. Supports `ManagerClass` / `managerOptions` for swappable managers |
| `Canvas`       | Owns scene, camera instance, and renderer                                                                                                |
| `Camera`       | `PerspectiveCamera` / `OrthographicCamera` wrapper with `getViewSize()`                                                                  |
| `Renderer`     | `THREE.WebGLRenderer` wrapper (alpha, high-performance, DPR capped at 2, AA off)                                                         |
| `RenderTarget` | `THREE.WebGLRenderTarget` wrapper with size-change guard                                                                                 |
| `Materials`    | Caching material + HDR env map manager (auto-unwraps renderer wrapper)                                                                   |
| `PostPass`     | Full-screen post-processing: scene → RT → custom ShaderMaterial quad                                                                     |
| `ShaderPlane`  | `PlaneGeometry(1,1)` + `ShaderMaterial` — fullscreen or DOM-tracked, with built-in `uTime` / `uResolution` uniforms                      |
| `WorldManager` | Per-DOM-element GL component manager (`[data-{name}]` selectors) — always-on, all components tick every frame                            |
| `ScrollableWM` | Extends `WorldManager` — adds `isActive` gating, `scrollProgress` (0→1), and `onEnter`/`onLeave` lifecycle hooks                         |

## Loaders

| Loader        | Description                                                                  |
| ------------- | ---------------------------------------------------------------------------- |
| `ModelLoader`  | GLTFLoader + DRACOLoader with batch API (`loadAll`)                                                       |
| `AssetLoader`  | **Preferred.** Unified manifest loader — `texture`, `cubeTexture`, `gltf`, `hdr`. Promise + event API.   |
| `Resources`    | **Deprecated.** Texture + cube texture only, event API. Kept for backward compat — use `AssetLoader` instead. |

## Utils

| Util              | Description                                                                                           |
| ----------------- | ----------------------------------------------------------------------------------------------------- |
| `Tracker`         | Tracks a DOM element's bounds → `position` + `size` in 3D world space                                 |
| `DOMSync`         | DOM → mesh sync for both Lenis smooth-scroll (desktop) and native scroll (mobile) — same API for both |
| `boundsToView`    | `DOMRect` → world `{ position, size }` at camera plane                                                |
| `boundsToViewAtZ` | `DOMRect` → world `{ position, size }` at arbitrary Z depth                                           |
| `OrbitControls`   | `THREE.OrbitControls` wrapper that integrates with `App` services pattern                             |
| `Theatre`         | Theatre.js (`@theatre/core`) service wrapper                                                          |
| `dracoPath`       | Draco CDN URL constant used by `ModelLoader`                                                          |

---

## Usage

### Full-page single world

```js
import { setupKit } from "@linear_non/stellar-kit"
import { App } from "@linear_non/stellar-gl/classes"

setupKit()

class MyWorld {
  constructor({ scene, camera, gl, services }) { ... }
  update({ time, current }) { ... }
  resize() { ... }
  destroy() { ... }
}

const app = new App({ canvas: document.querySelector("canvas"), WorldClass: MyWorld })
```

### Modular DOM-synced GL components

```js
const app = new App({
  canvas: document.querySelector("canvas"),
  components: [
    { name: "hero", Class: GLHero },
    { name: "slider", Class: GLSlider },
  ],
})
// Each class receives: { el, index, scene, camera, gl, services }
// Mapped via [data-hero], [data-slider] attributes
```

### Post-processing

```js
import { PostPass } from "@linear_non/stellar-gl/classes"

const app = new App({
  canvas,
  WorldClass: MyWorld,
  services: {
    post: new PostPass({
      fragmentShader: myFrag,
      uniforms: { uIntensity: { value: 0.5 } },
    }),
  },
})
```

### Model loading

```js
import { ModelLoader } from "@linear_non/stellar-gl/loaders"

const app = new App({
  canvas,
  WorldClass: MyWorld,
  services: { models: new ModelLoader() },
})

// In MyWorld.constructor:
const { hero } = await this.services.models.loadAll({ hero: "/models/hero.glb" })
this.scene.add(hero)
```

---

## Local Development

```bash
npm install
npm run dev
```

Dev pages:

| Route         | Demo                                       |
| ------------- | ------------------------------------------ |
| `/`           | Index                                      |
| `/world/`     | Spinning cube + ModelLoader                |
| `/particles/` | GPU tube particles                         |
| `/manager/`   | DOM-synced planes + backgroundSize shader  |
| `/loaders/`      | GLTF + DRACO + OrbitControls               |
| `/asset-loader/` | AssetLoader — GLTF + HDR env map + progress |
| `/fbo/`          | FBOSystem — 16 384 curl-noise particles advanced via GPGPU ping-pong |
| `/post/`      | PostPass — chromatic aberration + vignette |
| `/rt/`        | RenderTarget — off-screen scene on a cube  |
| `/theatre/`   | Theatre.js Studio + sequence keyframes     |

---

## Roadmap

### High priority

- [x] **`GlobalUniforms` singleton** (`classes/GlobalUniforms.js`) — `{ uTime, uResolution, uDpr }` as plain `{ value }` refs. Spread into any `ShaderMaterial` — `App` keeps them in sync every tick and resize. No UBO/WebGL2 needed.
- [x] **`time.scale` on App clock** — `app.timeScale` (default `1`). Scales delta and `uTime`. Existing code unaffected.
- [x] **`AssetLoader`** (`loaders/AssetLoader.js`) — unified manifest loader. Supports `texture`, `cubeTexture`, `gltf`, `hdr`. Promise API (`await loader.load(sources)`) + event API. Key-based deduplication. `Resources` kept untouched.
- [x] **`Tracker` as `Object3D` subclass** — `Tracker` now extends `THREE.Object3D`. `scene.add(tracker)`, `tracker.add(mesh)`. Fully backward-compatible.
- [x] **GLSL shader chunks library** (`shaders/chunks/`) — `cnoise`, `simplex`, `mapRange`, `rotate2d`, `rotate3d`, `backgroundCover` as JS template-literal GLSL. Import from `@linear_non/stellar-gl/shaders/chunks`.
- [x] **`onBeforeCompile` shader injector** (`utils/shaderInjector.js`) — `injectShader(mat, { uniforms, vertex, fragment })` replaces `#include` tokens in Three.js built-in materials. Sets `customProgramCacheKey`, warns on unknown tokens.
- [x] **`MeshSampler` utility** (`utils/MeshSampler.js`) — `sampleSurface`, `sampleVertices`, `sampleToTexture`. Wraps `MeshSurfaceSampler` for particle `BufferAttribute`s and GPGPU `DataTexture` seeds.
- [x] **GPU performance tier** (`utils/gpuTier.js`) — `getTier()` → `1|2|3`, `tierScale([0,64,96,128])`, `setTierOverride()`. Tier 1 = mobile, 2 = Safari desktop, 3 = all other desktop.
- [x] **`assignMaterials` model traversal util** (`utils/assignMaterials.js`) — `assignMaterials(gltfRoot, { 'Wheel*': mat, '*Glass*': glassMat })` with glob wildcards. Returns `Map<Mesh, Material>` of all updated meshes.

### Medium priority

- [ ] **`ScrollableWM` cross-fade hook** — optional `onTransition(from, to, progress)` callback called each frame as `scrollProgress` changes between two active components.
- [x] **Three.js `Layers` helper** (`utils/layers.js`) — `createLayerMap()` + `setLayer(object3D, layer)` for per-section render isolation. Shipped v1.4.0.
- [ ] **`ktx2` type in `AssetLoader`** — `{ name, type: 'ktx2', path }` support via `KTX2Loader` (Basis Universal / ETC1S). GPU-native decompression; smaller wire size; no CPU decode stall. Requires `transcoderPath` constructor option.

### Lower priority

- [x] **`FBOSystem` GPGPU wrapper** (`classes/FBOSystem.js`) — ping-pong `WebGLRenderTarget` helper (no external dependencies). `addVariable(name, shader, seed)`, `compute(time)`, `get(name)` API. `createParticleGeometry()` builds the `reference` UV attribute. Integrates with `gpuTier` for texture size scaling and `MeshSampler` for surface-seeded initial positions. Shipped v1.4.0.
- [ ] **`App` async init path** — `async init()` gates first render behind `await resources.load(manifest)` to prevent blank frames.
- [ ] **`CrossFadePass`** — postprocessing pass with two `WebGLRenderTarget`s and a `progress` uniform; drives scroll-based scene transitions.
- [ ] **Theatre.js per-scene wrapper** — `createSheet(project, sheet, state)` helper reducing boilerplate for per-scene authoring.

### Nice to have

- [ ] **Debug stats via URL param** — auto-mount `three-perf` overlay when `?debug` is active. Zero overhead in production.
- [ ] **`DestroyManager` mixin** — tracks all `IntersectionObserver`s, event listeners, and timers for automatic flush on `destroy()`.

### Completed

- [x] Post-processing pipeline — shipped as `PostPass`
- [x] Theatre.js service — `Theatre` class bundled with `@theatre/core` as a direct dependency (`@theatre/studio` dev-only)
- [x] `ModelLoader` — shipped in `loaders/ModelLoader.js`
- [x] `RenderTarget` support — shipped in `classes/RenderTarget.js`
- [x] `Renderer.dispose()` fix (B1)
- [x] `WorldManager` drops `services` fix (B2)
- [x] `Resources` hangs on unknown types fix (B3)
- [x] `boundsToViewAtZ` raw camera expectation documented (B4)
- [x] README antialias correction (B5)
- [x] `boundsToView` + `Tracker` container-aware (B6)
- [x] `Renderer.resize()` updates pixel ratio (B7)
- [x] `Materials` auto-unwraps renderer wrapper (B8)
- [x] `Resources` empty-sources emits with `items` (B9)

---

Made with ❤️ by [Non-Linear Studio](https://non-linear.studio)