Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 142 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Overview

SNOWFLOW is a real-time procedural snow rendering tech demo demonstrating advanced WebGPU techniques including:

- **Nested-ring clipmap terrain** with CDLOD morphing and GPU-side displacement
- **Multi-scale snow shading** with anisotropic sastrugi, ripples, triplanar mapping, and procedural view-dependent glints
- **Persistent terrain deformation** through a ping-ponged state buffer
- **Procedural character** with bone-based skeleton, Verlet cloth simulation, and shell fur
- **Five spell VFX systems** sharing a water body and light pool
- **Post-processing chain** with TAA, bloom, volumetric light shafts, DoF, and AgX tonemapping

### Auto-Performance Mode

The demo includes an automated performance system with multiple characters that can perform:

- **Demonstration mode**: Synchronized character choreography with spell effects
- **Combat mode**: AI-driven character battles with spell casting
- **Ceremonial mode**: Group ceremony with synchronized poses
- **Free mode**: Individual characters with autonomous behavior

Access via `SNOWFLOW.automation` and `SNOWFLOW.charManager` in the console.

### Multi-Scene Support

The demo supports multiple terrain types and environments:

- **Snow Field** (default): Classic snowy landscape with dunes and sastrugi
- **Desert**: Sandy dunes, rocky outcrops, warm lighting
- **Ocean Coast**: Coastal with waves, beaches, and water bodies
- **Forest**: Trees, vegetation, dense canopy
- **Urban Street**: City streets, buildings, paved surfaces

Switch scenes via `SNOWFLOW.sceneManager.switchTo(type)` or use the scene switcher UI (F1 to toggle).

## Architecture

```
src/
main.js Entry point, frame orchestration, system initialization
core/ Settings, input, camera rig, performance tracking, GPU helpers
terrain/ Heightfield baking, clipmap mesh, deformation state buffer
render/ Sky atmosphere, IBL, shadow cascades, depth prepass
character/ Skeleton, procedural geometry generation, Verlet cloth solver
vfx/ Particle pools, snow-surf wake mesh
spells/ Five spell types, water body, light pool
post/ Full post-processing chain (TAA, bloom, SSR, DoF)
performance/ Auto-performance system (new in this version)
scene/ Multi-scene manager (new in this version)
shaders/ All WGSL - lib/ contains shared includes
```

### Key Design Patterns

1. **GPU-driven terrain**: Clipmap vertices only store `(gridIndex, ringLevel)`; world placement and displacement happen entirely in the vertex shader via `clipmap.wgsl` include.

2. **Persistent deformation buffer**: Two 2048² RGBA16F targets store depression depth, displaced mass, compression, and ice. The buffer is toroidally addressed so it follows the player without copying.

3. **Procedural character**: No rig files or animation clips. Skeleton bind pose is a table of numbers; garments are Verlet-clothed surfaces with shape-memory constraints.

4. **Shared lighting**: Four dynamic lights per frame share `snowSubsurface` through `spellLights.wgsl`, enabling spells to light snow through terrain crests.

5. **Prepass architecture**: A camera-space depth prepass feeds TAA, SSR, volumetric shafts, and DOF while enabling the beauty pass to use refraction without scene copies.

## Commands

```bash
npm install
npm run dev # Vite dev server on :5173
npm run build # Production build into dist/
npm run preview # Serve production build
```

## Dependencies

- `@babylonjs/core` (^9.18.0) - Engine and scene graph
- `@babylonjs/materials` (^9.18.0) - Material implementations
- `vite` (^8.1.5) - Build tool (dev only)

## WebGPU Requirements

- Chrome/Edge 113+, Firefox 141+, or Safari 26+
- Discrete or recent integrated GPU with `textureFloatLinearFiltering` support
- No WebGL fallback - the demo checks for `navigator.gpu` and stops if absent

## Auto-Performance API

```javascript
// Access the automation engine
SNOWFLOW.automation.startShow(SNOWFLOW.automation.createDemonstrationShow())
SNOWFLOW.automation.togglePause()
SNOWFLOW.automation.stopShow()

// Access the character manager
SNOWFLOW.charManager.setMode('demonstration')
SNOWFLOW.charManager.performAction(0, 'spin')

// Access the show selector UI
SNOWFLOW.showSelector.show()
SNOWFLOW.showSelector.hide()

// Available shows
SNOWFLOW.automation.createDemonstrationShow()
SNOWFLOW.automation.createCombatShow()
SNOWFLOW.automation.createCeremonialShow()
```

## Scene Management API

```javascript
// Switch to a scene by type
SNOWFLOW.sceneManager.switchTo('desert')
SNOWFLOW.sceneManager.switchTo('ocean')
SNOWFLOW.sceneManager.switchTo('forest')
SNOWFLOW.sceneManager.switchTo('urban')

// Switch to next/previous scene
SNOWFLOW.sceneManager.switchNext()
SNOWFLOW.sceneManager.switchPrev()

// Get current scene info
SNOWFLOW.sceneManager.getSceneInfo()

// Toggle scene switcher UI
SNOWFLOW.sceneSwitcher.toggle(SNOWFLOW.sceneManager)
```

## Console Commands

| Command | Description |
|---------|-------------|
| `SNOWFLOW.automation.startShow(SNOWFLOW.automation.createDemonstrationShow())` | Start demo show |
| `SNOWFLOW.automation.togglePause()` | Pause/resume current show |
| `SNOWFLOW.automation.stopShow()` | Stop current show |
| `SNOWFLOW.charManager.setMode('combat')` | Switch to combat mode |
| `SNOWFLOW.showSelector.toggle()` | Toggle show selector UI |
| `SNOWFLOW.sceneManager.switchTo('desert')` | Switch to desert scene |
| `SNOWFLOW.sceneManager.switchNext()` | Switch to next scene |
| `SNOWFLOW.sceneSwitcher.toggle()` | Toggle scene switcher UI |
65 changes: 58 additions & 7 deletions src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,16 @@ import { PostChain } from "./post/postChain.js";
import { whenReady } from "./core/gpuUtil.js";
import * as loading from "./core/loading.js";

import { CharacterManager } from "./performance/characterManager.js";
import {
AutomationEngine,
createDemonstrationShow,
createCombatShow,
createCeremonialShow
} from "./performance/automation.js";
import { ShowSelectorUI } from "./performance/showSelector.js";
import { SceneManager, SceneSwitcherUI } from "./scene/sceneManager.js";

// ------------------------------------------------------- module-scope scratch
const _vel = new Vector3();

Expand Down Expand Up @@ -163,7 +173,41 @@ async function boot() {
const post = new PostChain(scene, rig.camera, depthPass, sky);

const overlay = new Overlay({ rig, character });
initInput(canvas, { onToggleOverlay: () => overlay.toggle() });

// ------------------------------------------------- character manager & show system
const charManager = new CharacterManager(scene, terrain, sky, shadows);
charManager.initialize(5); // 5 characters for demonstration

const automation = new AutomationEngine(scene, rig, charManager, spells);
const showSelector = new ShowSelectorUI();

// ----------------------------------------------------------- scene manager
const sceneManager = new SceneManager();
const sceneSwitcher = new SceneSwitcherUI();
sceneSwitcher.sync(sceneManager);

// Show selector toggle (F1 toggles overlay, which toggles both UIs)
initInput(canvas, {
onToggleOverlay: () => {
overlay.toggle();
if (overlay.container.classList.contains("show")) {
showSelector.hide();
sceneSwitcher.hide();
} else {
showSelector.show();
sceneSwitcher.showUI();
}
}
});

// Global access for console control
globalThis.SNOWFLOW = {
engine, scene, rig, character, figure, contact, spray, wake, spells,
overlay, terrain, sky, shadows, post, depthPass,
S, input, perfStats: stats,
charManager, automation, showSelector,
sceneManager, sceneSwitcher,
};

// ------------------------------------------------------------- warm-up
// Everything that can compile, compiles here — behind the loading screen.
Expand Down Expand Up @@ -275,6 +319,15 @@ async function boot() {
spells.triangles +
spray.liveCount * 2;

// Update character manager (AI characters)
charManager.update(dt);

// Update automation engine (shows)
automation.update(dt);

// Update lighting interpolation from automation
automation.updateLighting(dt);

sample(dtMs);
checkSpike(dtMs);
overlay.update(dtMs, engine);
Expand All @@ -284,12 +337,10 @@ async function boot() {

await loading.done();
setTimeout(() => overlay.resetSpikes(), 800);

globalThis.SNOWFLOW = {
engine, scene, rig, character, figure, contact, spray, wake, spells,
overlay, terrain, sky, shadows, post, depthPass,
S, input, perfStats: stats,
};
setTimeout(() => {
showSelector.show();
automation.startShow(createDemonstrationShow());
}, 500);
}

boot().catch((err) => {
Expand Down
Loading