@call-me-sensei/toonlab 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +127 -4
- package/ATTRIBUTION.md +41 -3
- package/README.md +234 -34
- package/docs/characters.md +144 -0
- package/docs/debug-panel.md +126 -0
- package/docs/docs.css +589 -0
- package/docs/environment.md +186 -0
- package/docs/getting-started.md +180 -0
- package/docs/index.html +14 -0
- package/docs/lab-architecture.md +100 -0
- package/docs/lighting.md +825 -0
- package/docs/main.jsx +739 -0
- package/docs/mcp.md +97 -0
- package/docs/post-processing.md +98 -0
- package/docs/settings-reference.md +1878 -0
- package/docs/shader-constants.md +77 -0
- package/docs/sky.md +182 -0
- package/docs/style-labs.md +309 -0
- package/docs/texture-lab.md +135 -0
- package/docs/toon-shading.md +183 -0
- package/docs/tsl-conventions.md +167 -0
- package/docs/vegetation-sky.md +275 -0
- package/docs/water.md +430 -0
- package/docs/weather.md +200 -0
- package/docs/world-scale.md +111 -0
- package/mcp/server.mjs +610 -0
- package/mcp/style-lab-tools.mjs +371 -0
- package/mcp/vite-plugin.mjs +174 -0
- package/mcp/workspace.mjs +397 -0
- package/package.json +64 -5
- package/src/ambientfx/INTEGRATION.md +164 -0
- package/src/ambientfx/ambientFxPresets.js +83 -0
- package/src/ambientfx/ambientFxSettings.js +368 -0
- package/src/ambientfx/emitters.js +169 -0
- package/src/ambientfx/index.js +5 -0
- package/src/ambientfx/particleBackbone.js +493 -0
- package/src/ambientfx/stylizedAmbientFx.js +450 -0
- package/src/assetlib/ambientcg.js +117 -0
- package/src/assetlib/assetRef.js +91 -0
- package/src/assetlib/importedEntry.js +40 -0
- package/src/assetlib/index.js +23 -0
- package/src/assetlib/kaykit.js +192 -0
- package/src/assetlib/kaykitStaticIndex.js +700 -0
- package/src/assetlib/loadImported.js +143 -0
- package/src/assetlib/opensource3d.js +113 -0
- package/src/assetlib/polyhaven.js +182 -0
- package/src/assetlib/polypizza.js +115 -0
- package/src/assetlib/smithsonian.js +214 -0
- package/src/assetlib/sources.js +279 -0
- package/src/assetlib/zip.js +58 -0
- package/src/biome/biomeGenerator.js +385 -0
- package/src/biome/biomeRuntime.js +299 -0
- package/src/biome/index.js +2 -0
- package/src/buildinggen/buildingAsset.js +44 -0
- package/src/buildinggen/buildingGrammar.js +311 -0
- package/src/buildinggen/buildingMesh.js +451 -0
- package/src/buildinggen/buildingPresets.js +46 -0
- package/src/buildinggen/buildingRecipe.js +100 -0
- package/src/buildinggen/buildingSettings.js +238 -0
- package/src/buildinggen/index.js +6 -0
- package/src/camera/cameraDirector.js +157 -0
- package/src/camera/cameraGenerator.js +367 -0
- package/src/camera/cameraRig.js +570 -0
- package/src/camera/cameraSettings.js +236 -0
- package/src/camera/index.js +7 -0
- package/src/catalog/builtinEntries.js +245 -0
- package/src/catalog/catalog.js +210 -0
- package/src/catalog/index.js +3 -0
- package/src/catalog/manifest.js +84 -0
- package/src/core/generation.js +529 -0
- package/src/environment/environmentRigs.js +21 -1
- package/src/environment/environmentSettings.js +4 -0
- package/src/environment/environmentSunShadowPass.js +8 -0
- package/src/fauna/INTEGRATION.md +174 -0
- package/src/fauna/boids.js +861 -0
- package/src/fauna/faunaBodies.js +492 -0
- package/src/fauna/faunaPresets.js +52 -0
- package/src/fauna/faunaSettings.js +525 -0
- package/src/fauna/index.js +5 -0
- package/src/fauna/stylizedFauna.js +395 -0
- package/src/game-feel/gameFeelGenerator.js +402 -0
- package/src/game-feel/gameFeelRuntime.js +549 -0
- package/src/game-feel/index.js +2 -0
- package/src/index.js +17 -6
- package/src/lighting/colorIntensity.js +177 -0
- package/src/lighting/index.js +161 -0
- package/src/lighting/lightDescriptors.js +249 -0
- package/src/lighting/lightingCapabilities.js +79 -0
- package/src/lighting/lightingDocuments.js +247 -0
- package/src/lighting/lightingFixtures.js +446 -0
- package/src/lighting/lightingGenerator.js +449 -0
- package/src/lighting/lightingPresets.js +319 -0
- package/src/lighting/lightingRuntime.js +723 -0
- package/src/lighting/lightingStyle.js +386 -0
- package/src/lighting/lightingSystem.js +774 -0
- package/src/lighting/unrealExport.js +186 -0
- package/src/lighting/utils.js +87 -0
- package/src/motion/index.js +5 -0
- package/src/motion/motionClip.js +441 -0
- package/src/motion/motionController.js +628 -0
- package/src/motion/motionDocuments.js +225 -0
- package/src/motion/motionGraph.js +307 -0
- package/src/motion/motionSettings.js +222 -0
- package/src/pathgen/index.js +7 -0
- package/src/pathgen/pathBridge.js +232 -0
- package/src/pathgen/pathPresets.js +35 -0
- package/src/pathgen/pathRibbon.js +410 -0
- package/src/pathgen/pathRouter.js +380 -0
- package/src/pathgen/pathSettings.js +335 -0
- package/src/pathgen/pathTextures.js +123 -0
- package/src/pathgen/stylizedPaths.js +453 -0
- package/src/post/index.js +1 -0
- package/src/post/postGenerator.js +177 -0
- package/src/post/postProcessing.js +41 -0
- package/src/propgen/generatorsWave1.js +379 -0
- package/src/propgen/generatorsWave2.js +462 -0
- package/src/propgen/index.js +5 -0
- package/src/propgen/propAsset.js +323 -0
- package/src/propgen/propParts.js +170 -0
- package/src/propgen/propPlacement.js +459 -0
- package/src/propgen/propPresets.js +82 -0
- package/src/propgen/propSettings.js +395 -0
- package/src/shaders-tsl/chunks/projected-water-caustics.js +242 -0
- package/src/shaders-tsl/chunks/vegetation-style.js +360 -0
- package/src/shaders-tsl/chunks/water-shore-state.js +31 -0
- package/src/shaders-tsl/chunks/water-waves.js +90 -11
- package/src/shaders-tsl/environment.js +15 -1
- package/src/shaders-tsl/flower.js +279 -30
- package/src/shaders-tsl/grass.js +60 -33
- package/src/shaders-tsl/sky.js +125 -31
- package/src/shaders-tsl/tree-leaf.js +61 -25
- package/src/shaders-tsl/water-breaker.js +7 -4
- package/src/shaders-tsl/water-shore-state-simulation.js +523 -0
- package/src/shaders-tsl/water.js +439 -49
- package/src/shaders-tsl/woody-surface.js +154 -0
- package/src/sky/sceneOverrideLayers.js +10 -0
- package/src/sky/skyQuality.js +26 -0
- package/src/sky/stylizedSky.js +753 -45
- package/src/soundscape/index.js +4 -0
- package/src/soundscape/soundscapeGenerator.js +179 -0
- package/src/soundscape/soundscapeRuntime.js +806 -0
- package/src/soundscape/soundscapeSettings.js +292 -0
- package/src/styles/index.js +13 -0
- package/src/styles/styleBundle.js +325 -0
- package/src/stylizedTerrain.js +32 -2
- package/src/stylizedWorld.js +423 -20
- package/src/texgen/evaluateTexture.js +675 -0
- package/src/texgen/index.js +60 -0
- package/src/texgen/noise2.js +210 -0
- package/src/texgen/textureAi.js +436 -0
- package/src/texgen/textureGenerators.js +516 -0
- package/src/texgen/texturePresets.js +490 -0
- package/src/texgen/textureSettings.js +342 -0
- package/src/texgen/textureThree.js +59 -0
- package/src/vegetation/flowerSpecies.js +15 -3
- package/src/vegetation/grassPalettes.js +153 -0
- package/src/vegetation/index.js +6 -0
- package/src/vegetation/stylizedBush.js +2 -0
- package/src/vegetation/stylizedFlower.js +82 -0
- package/src/vegetation/stylizedFlowers.js +48 -7
- package/src/vegetation/stylizedForest.js +29 -1
- package/src/vegetation/stylizedGrass.js +291 -56
- package/src/vegetation/stylizedTree.js +38 -2
- package/src/vegetation/stylizedTreeFoliage.js +2 -1
- package/src/vegetation/vegetationShaders.js +1110 -0
- package/src/vfxgen/INTEGRATION.md +145 -0
- package/src/vfxgen/core/burstBackbone.js +380 -0
- package/src/vfxgen/core/projectileCore.js +92 -0
- package/src/vfxgen/core/spriteShapes.js +98 -0
- package/src/vfxgen/core/trailRibbon.js +272 -0
- package/src/vfxgen/core/vfxRandom.js +29 -0
- package/src/vfxgen/effects/emitHelpers.js +37 -0
- package/src/vfxgen/effects/magicEffects.js +162 -0
- package/src/vfxgen/effects/movementEffects.js +87 -0
- package/src/vfxgen/effects/weaponEffects.js +118 -0
- package/src/vfxgen/index.js +18 -0
- package/src/vfxgen/moves/moveController.js +146 -0
- package/src/vfxgen/moves/moveLibrary.js +335 -0
- package/src/vfxgen/vfxPresets.js +98 -0
- package/src/vfxgen/vfxSettings.js +384 -0
- package/src/vfxgen/vfxSystem.js +449 -0
- package/src/vfxgen/weapons/stylizedWeapons.js +137 -0
- package/src/villagegen/index.js +4 -0
- package/src/villagegen/stylizedVillage.js +490 -0
- package/src/villagegen/villageArchetypes.js +160 -0
- package/src/villagegen/villageNames.js +40 -0
- package/src/villagegen/villageSites.js +105 -0
- package/src/water/sceneOverrideLayers.js +23 -0
- package/src/water/water.js +5 -0
- package/src/water/waterBreakerSystem.js +15 -1
- package/src/water/waterCurrentField.js +447 -0
- package/src/water/waterMaterial.js +12 -0
- package/src/water/waterNearshorePhase.js +320 -0
- package/src/water/waterScenePasses.js +83 -30
- package/src/water/waterSettings.js +325 -13
- package/src/water/waterShoreMaterial.js +322 -0
- package/src/water/waterShoreStateField.js +605 -0
- package/src/water/waterSurface.js +797 -28
- package/src/weather/index.js +6 -0
- package/src/weather/weatherPrecipitation.js +221 -0
- package/src/weather/weatherPresets.js +258 -0
- package/src/weather/weatherSettings.js +269 -0
- package/src/weather/weatherSystem.js +871 -0
- package/src/worldMinimap.js +62 -0
- package/src/worldPresets.js +4 -1
package/docs/water.md
ADDED
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
# Water
|
|
2
|
+
|
|
3
|
+
A modern anime-style stylized, interactive water system. Fully procedural — no
|
|
4
|
+
texture assets — with the whole wave spectrum mirrored on the CPU so physics
|
|
5
|
+
and rendering always agree.
|
|
6
|
+
|
|
7
|
+
Water materials and simulation passes are TSL-only. They run on native WebGPU
|
|
8
|
+
by default and on the TSL WebGL2 fallback with `?renderer=webgl`.
|
|
9
|
+
|
|
10
|
+
Water Lab authors one complete runtime-system preset. Its Surface, Foam, and
|
|
11
|
+
Lighting groups are the embedded water-shader controls; Waves, Ripples,
|
|
12
|
+
Splashes, and Quality own the coupled runtime behavior. ToonLab intentionally
|
|
13
|
+
does not split those values into a separate Water Shader Lab, which would
|
|
14
|
+
produce two documents with overlapping ownership. See
|
|
15
|
+
[Lab responsibilities](lab-architecture.md).
|
|
16
|
+
|
|
17
|
+
## Quickstart
|
|
18
|
+
|
|
19
|
+
```js
|
|
20
|
+
import { WaterSurface } from '@call-me-sensei/toonlab/water';
|
|
21
|
+
import { StylizedSky } from '@call-me-sensei/toonlab/sky';
|
|
22
|
+
|
|
23
|
+
const water = new WaterSurface({
|
|
24
|
+
width: 200, depth: 200, preset: 'lake',
|
|
25
|
+
// Optional terrain sampler enabling surf mechanics: waves shoal, break,
|
|
26
|
+
// and wash a swash film up the beach. breakerAmount > 0 adds plunging
|
|
27
|
+
// breaker shells that ride each set wave to the break line.
|
|
28
|
+
bedHeight: (x, z) => terrainHeightAt(x, z),
|
|
29
|
+
});
|
|
30
|
+
water.position.y = 0.4;
|
|
31
|
+
scene.add(water);
|
|
32
|
+
|
|
33
|
+
const sky = new StylizedSky(); // shows up in the water's reflections
|
|
34
|
+
scene.add(sky);
|
|
35
|
+
|
|
36
|
+
water.addInteractor(characterObject3D, { radius: 0.35 });
|
|
37
|
+
water.setFollowTarget(characterObject3D); // ripple window follows across big water
|
|
38
|
+
|
|
39
|
+
// Current scene light may replace the preset's standalone fallback values.
|
|
40
|
+
water.setSceneOverrides({
|
|
41
|
+
sunDirection: lightingState.sunDirection,
|
|
42
|
+
sunColor: lightingState.sunColor,
|
|
43
|
+
skyZenithColor: lightingState.skyZenithColor,
|
|
44
|
+
skyHorizonColor: lightingState.skyHorizonColor,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
// per frame, before renderer.render(scene, camera):
|
|
48
|
+
water.update(renderer, scene, camera, delta);
|
|
49
|
+
sky.update(delta, camera);
|
|
50
|
+
|
|
51
|
+
// interactions & physics
|
|
52
|
+
water.splash({ x, y, z }, { strength: 1.2 });
|
|
53
|
+
water.addRipple({ x, z }, { radius: 0.3, strength: 0.5 });
|
|
54
|
+
const surfaceY = water.getHeightAt(x, z); // buoyancy — includes swell + breakers
|
|
55
|
+
const flow = water.getFlowAt(x, z); // surge velocity (breaker whitewater)
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
(Inside this repo the labs import from `../../src/water/...`.)
|
|
59
|
+
|
|
60
|
+
Constraints: the surface must stay axis-aligned (translation only), and
|
|
61
|
+
depth-based effects assume a perspective camera. Above water,
|
|
62
|
+
`WaterScenePasses` renders a submerged-scene color+depth grab and a planar
|
|
63
|
+
reflection. Below water, that color target becomes a same-pose air-side
|
|
64
|
+
transmission capture with an oblique water-plane clip, while the planar
|
|
65
|
+
reflection and scene-depth pass are skipped. Exclude objects with
|
|
66
|
+
`userData.waterExclude` (all water passes), `userData.waterGrabExclude`
|
|
67
|
+
(above-water grab only), or `userData.waterReflectionExclude` (reflection
|
|
68
|
+
only).
|
|
69
|
+
|
|
70
|
+
## Settings, presets, tones
|
|
71
|
+
|
|
72
|
+
Water settings are flat (`createWaterSettings({ preset: 'ocean',
|
|
73
|
+
waveIntensity: 0.6 })`); all 82 fields across 7 groups (waves, surface,
|
|
74
|
+
foam, lighting, ripples, splashes, quality) are in the
|
|
75
|
+
[settings reference](settings-reference.md). Highlights:
|
|
76
|
+
|
|
77
|
+
- **`waveIntensity`** — the authored baseline scales the whole Gerstner
|
|
78
|
+
spectrum from glassy mirror to storm swell. Components are slope-limited,
|
|
79
|
+
so big dials stretch to long wavelengths instead of spiking. Current
|
|
80
|
+
Weather may transiently modulate this baseline without editing the preset.
|
|
81
|
+
- **Wave sets** — `waveSetPeriod`/`waveSetStrength` make big waves arrive in
|
|
82
|
+
groups with lulls between, marching at group velocity.
|
|
83
|
+
- **Body color** — three-stop absorption (`shallowColor → midColor →
|
|
84
|
+
deepColor`), separate from wave motion. `colorTone` picks a named palette
|
|
85
|
+
from `WATER_COLOR_TONES`: `classic`, `anime`, `teal`, `caribbean`,
|
|
86
|
+
`lagoon`, `deepOcean`.
|
|
87
|
+
- **Underwater transmission** — `indexOfRefraction` anchors the Snell window
|
|
88
|
+
and total internal reflection; `underwaterTransmission` controls how much
|
|
89
|
+
of the captured air-side scene comes through; `underwaterTintStrength`
|
|
90
|
+
keeps that view inside the authored water palette.
|
|
91
|
+
- **Shore** — `shoalingDepth`, `shorelineWaves`, `shorelineRunup`, and
|
|
92
|
+
`runupDistance` tune surf and swash reach. With an explicit
|
|
93
|
+
`runupDistance`, event peaks vary over 80–100% of that bound and the next
|
|
94
|
+
uprush starts at the preceding rundown endpoint rather than resetting.
|
|
95
|
+
`breakerEnabled/breakerAmount/breakerCurl/breakerScale/breakerPeel`
|
|
96
|
+
control the plunging-breaker shells.
|
|
97
|
+
- **Persistent beach state** — `swashFoamAmount`, `swashFoamLifetime`,
|
|
98
|
+
`swashFoamResidueLifetime`, `wetSandDryTime`, `wetSandDarkening`, and
|
|
99
|
+
`wetSandSheen` are separate from offshore `foamAmount`. They take effect
|
|
100
|
+
when the surface is constructed with a `shoreState` field and the beach
|
|
101
|
+
uses a shore-state material.
|
|
102
|
+
|
|
103
|
+
Built-in presets (`WATER_PRESET_NAMES`): `mirror`, `calm`, `lake`, `river`,
|
|
104
|
+
`coast`, `ocean`, `storm` — plus `call_me_sensei`, the studio-managed
|
|
105
|
+
signature preset (curated and updated over releases). Apply at construction
|
|
106
|
+
(`preset:`) or live with `water.setPreset(name, overrides)` /
|
|
107
|
+
`water.applySettings(options)`.
|
|
108
|
+
|
|
109
|
+
### Authored baseline vs. current scene
|
|
110
|
+
|
|
111
|
+
`water.settings` is always the authored, portable baseline. Body palette,
|
|
112
|
+
wave structure, foam, ripple/splash response, and water-specific lighting
|
|
113
|
+
response belong there. `waveIntensity` is also saved as the intended calmness
|
|
114
|
+
of that body of water.
|
|
115
|
+
|
|
116
|
+
`sunDirection`, `sunColor`, `skyZenithColor`, and `skyHorizonColor` remain
|
|
117
|
+
portable authored fallbacks so a water asset renders correctly in isolation,
|
|
118
|
+
previews, and scenes without a connected lighting rig. A live scene should
|
|
119
|
+
replace those current values transiently. Weather may likewise add temporary
|
|
120
|
+
wave energy:
|
|
121
|
+
|
|
122
|
+
```js
|
|
123
|
+
import {
|
|
124
|
+
WATER_SCENE_OVERRIDE_PRIORITIES,
|
|
125
|
+
} from '@call-me-sensei/toonlab/water';
|
|
126
|
+
|
|
127
|
+
const weatherLayer = Symbol('weather-water');
|
|
128
|
+
water.setSceneOverrideLayer(weatherLayer, (base) => ({
|
|
129
|
+
waveIntensity: Math.min(base.waveIntensity + currentWaterWaveBoost, 1),
|
|
130
|
+
}), { priority: WATER_SCENE_OVERRIDE_PRIORITIES.weather });
|
|
131
|
+
|
|
132
|
+
// Removes Weather only; a separate Lighting layer remains active.
|
|
133
|
+
water.clearSceneOverrideLayer(weatherLayer);
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
`setSceneOverrides()` is the convenience single-scene layer, and
|
|
137
|
+
`clearSceneOverrides()` removes only that layer. Named owners should clear their
|
|
138
|
+
own layer with `clearSceneOverrideLayer(id)`; `clearAllSceneOverrideLayers()` is
|
|
139
|
+
available for an explicit full teardown. `applySettings()` and `setPreset()`
|
|
140
|
+
keep their existing behavior as authored edits and automatically recompose
|
|
141
|
+
active runtime layers. `water.renderedSettings` exposes the composed state while
|
|
142
|
+
exports continue to read `water.settings`. Runtime layers accept only
|
|
143
|
+
`waveIntensity` and the four fallback sun/sky fields, so scene code cannot
|
|
144
|
+
accidentally overwrite the water palette or wave structure.
|
|
145
|
+
|
|
146
|
+
### Registering and sharing presets
|
|
147
|
+
|
|
148
|
+
```js
|
|
149
|
+
import {
|
|
150
|
+
registerWaterPreset,
|
|
151
|
+
serializeWaterPreset,
|
|
152
|
+
parseWaterPresetDocument,
|
|
153
|
+
registerSerializedWaterPreset,
|
|
154
|
+
} from '@call-me-sensei/toonlab/water';
|
|
155
|
+
|
|
156
|
+
registerWaterPreset('harbor', { waveIntensity: 0.25, colorTone: 'teal' });
|
|
157
|
+
|
|
158
|
+
// Versioned JSON document ('toonlab/water-preset'), same pattern as toon presets:
|
|
159
|
+
const json = serializeWaterPreset('harbor', { label: 'Harbor' });
|
|
160
|
+
const result = parseWaterPresetDocument(json);
|
|
161
|
+
if (result.ok) registerWaterPreset(result.value.id, result.value, { overwrite: true });
|
|
162
|
+
// or in one step: registerSerializedWaterPreset(json, { overwrite: true });
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
`getWaterPresetOptions()` lists built-ins plus registrations (for HUDs);
|
|
166
|
+
`validateWaterPresetDocument` / `createWaterPresetDocument` /
|
|
167
|
+
`sanitizeWaterPresetSettings` round out the document API.
|
|
168
|
+
|
|
169
|
+
## Persistent swash, foam, and wet sand
|
|
170
|
+
|
|
171
|
+
`shoreState` is opt-in because a lake or open-ocean tile does not necessarily
|
|
172
|
+
need a fixed beach-history atlas. It requires `bedHeight`, and its `region` is
|
|
173
|
+
world-anchored rather than camera-following. The four state channels are:
|
|
174
|
+
|
|
175
|
+
| Channel | Stored beach history |
|
|
176
|
+
|---|---|
|
|
177
|
+
| R | persistent sediment moisture |
|
|
178
|
+
| G | short-lived surface film |
|
|
179
|
+
| B | active aerated foam |
|
|
180
|
+
| A | stranded and drying foam residue |
|
|
181
|
+
|
|
182
|
+
The water samples this atlas so swash foam remains attached to the moving
|
|
183
|
+
wet/dry edge. A beach material can sample the same atlas, preserving wet sand,
|
|
184
|
+
the glossy draining film, and foam that has just crossed onto exposed sand.
|
|
185
|
+
The ground mesh must provide a `color` vertex attribute because
|
|
186
|
+
`createWaterShoreMaterial` uses it as the dry albedo. Optional `albedoMap`,
|
|
187
|
+
Poly Haven-style `armMap` (AO/roughness/metalness), `normalMap`, and
|
|
188
|
+
`textureRepeat` inputs can layer tiling grain detail under the same live wet
|
|
189
|
+
sand, foam, and projected-caustic response.
|
|
190
|
+
|
|
191
|
+
```js
|
|
192
|
+
import {
|
|
193
|
+
WaterSurface,
|
|
194
|
+
createWaterShoreMaterial,
|
|
195
|
+
} from '@call-me-sensei/toonlab/water';
|
|
196
|
+
|
|
197
|
+
const water = new WaterSurface({
|
|
198
|
+
width: 80,
|
|
199
|
+
depth: 40,
|
|
200
|
+
preset: 'coast',
|
|
201
|
+
bedHeight: (x, z) => terrainHeightAt(x, z),
|
|
202
|
+
runupDistance: 10, // event maxima vary from about 8 m to 10 m
|
|
203
|
+
nearshorePhase: true,
|
|
204
|
+
shoreState: {
|
|
205
|
+
region: { centerX: 0, centerZ: 0, width: 80, depth: 40 },
|
|
206
|
+
resolution: { x: 512, y: 256 },
|
|
207
|
+
},
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
const beachMaterial = createWaterShoreMaterial({
|
|
211
|
+
stateField: water.shoreState,
|
|
212
|
+
foamColor: water.settings.foamColor,
|
|
213
|
+
foamAmount: water.settings.swashFoamAmount,
|
|
214
|
+
wetDarkening: water.settings.wetSandDarkening,
|
|
215
|
+
});
|
|
216
|
+
beach.material = beachMaterial;
|
|
217
|
+
water.attachShoreStateMaterial(beachMaterial);
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
Call `water.attachShoreStateMaterial(material)` rather than binding only the
|
|
221
|
+
initial texture. The shore state uses ping-pong targets, and the attachment
|
|
222
|
+
refreshes the material after every swap. The field is a visual history model,
|
|
223
|
+
not a sediment, air-entrainment, or two-phase-fluid simulation.
|
|
224
|
+
|
|
225
|
+
## Quality tiers
|
|
226
|
+
|
|
227
|
+
`quality: 'low' | 'medium' | 'high'` gates the most expensive fragment
|
|
228
|
+
features (`WATER_QUALITY_TIERS`):
|
|
229
|
+
|
|
230
|
+
| Tier | Caustics/sparkles | Detail octaves | Foam octaves |
|
|
231
|
+
|---|---|---|---|
|
|
232
|
+
| `low` | off | 2 | 2 |
|
|
233
|
+
| `medium` | caustics + sparkles | 3 | 3 |
|
|
234
|
+
| `high` | + chromatic caustics | 4 | 3 |
|
|
235
|
+
|
|
236
|
+
Custom tiers are a plain object:
|
|
237
|
+
|
|
238
|
+
```js
|
|
239
|
+
new WaterSurface({ quality: { qualityLevel: 'high', detailOctaves: 5, foamOctaves: 4 } });
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
`resolveWaterQualityDefines(quality)` is the resolver if you build materials
|
|
243
|
+
directly (`createWaterMaterial`).
|
|
244
|
+
|
|
245
|
+
Water quality is a compile-time TSL graph policy. Pass it when constructing
|
|
246
|
+
`WaterSurface`; changing tiers requires replacing the surface/material graph.
|
|
247
|
+
Water Lab performs that rebuild while preserving the authored document and
|
|
248
|
+
stage state. `applySettings()` hot-updates art/simulation uniforms but should
|
|
249
|
+
not be used as a runtime quality switch. The saved `quality` value is the
|
|
250
|
+
preset's preferred deployment default; a host may replace it per device when
|
|
251
|
+
constructing the surface.
|
|
252
|
+
|
|
253
|
+
## The systems
|
|
254
|
+
|
|
255
|
+
`WaterSurface` orchestrates these modules (all exported from
|
|
256
|
+
`@call-me-sensei/toonlab/water` for standalone use):
|
|
257
|
+
|
|
258
|
+
- **`WaterRippleSimulation`** — GPU ping-pong heightfield with velocity,
|
|
259
|
+
foam energy, absorbing borders, and a texel-exact moving window that
|
|
260
|
+
follows a target across large surfaces.
|
|
261
|
+
- **`WaterCurrentField`** — optional CPU-authored, world-space horizontal
|
|
262
|
+
current atlas mirrored to a compact GPU texture. It can project authored
|
|
263
|
+
flow away from signed-distance obstacles and feeds gameplay queries plus
|
|
264
|
+
shore-foam transport. It does not solve pressure, circulation, separation,
|
|
265
|
+
or turbulence.
|
|
266
|
+
- **`WaterShoreStateField`** — optional world-anchored GPU ping-pong atlas for
|
|
267
|
+
moisture, surface film, active swash foam, and residue. Pair it with
|
|
268
|
+
**`createWaterShoreMaterial`** and `water.attachShoreStateMaterial(...)` so
|
|
269
|
+
the water and beach render the same history instead of looking like two
|
|
270
|
+
independent systems.
|
|
271
|
+
- **`WaterSplashSystem`** — GPU-ballistic droplet points, procedural spray
|
|
272
|
+
crown, expanding foam rings; all in-shader, no sprite atlas.
|
|
273
|
+
- **`WaterBreakerSystem`** — dedicated curl-shell geometry swept along the
|
|
274
|
+
break line: shells swell out of the ambient sea, pitch a plunging lip,
|
|
275
|
+
peel alongshore, and decay into a whitewater bore. Physical: mirrored on
|
|
276
|
+
the CPU (`sampleAt`) so `getHeightAt` rides objects over the passing face
|
|
277
|
+
and `getFlowAt` surges them shoreward. `breakerEnabled: false` removes the
|
|
278
|
+
whole system for perf A/B.
|
|
279
|
+
- **`WaterInteractionManager`** (via `water.addInteractor`) — objects
|
|
280
|
+
entering fast splash automatically, submerged movement leaves wakes with
|
|
281
|
+
bow spray, exits splash lighter. Interactors take a radius plus a height
|
|
282
|
+
(optionally a function for pose-dependent bodies).
|
|
283
|
+
- **`WaterRain`** — GPU-looping rain streaks to pair with ripple dimples.
|
|
284
|
+
- **`WaterKelpField`** — instanced kelp blades swaying with the flow.
|
|
285
|
+
- **Underwater view** — when the camera dips below the waterline, a clipped
|
|
286
|
+
same-pose scene capture supplies the real sky, clouds, and above-water
|
|
287
|
+
objects to a stylized IOR-based Snell window with total internal
|
|
288
|
+
reflection and compact Beer–Lambert-inspired tinting.
|
|
289
|
+
- **Projected floor caustics** — the environment and shore materials receive
|
|
290
|
+
a runtime-generated seamless Voronoi web sampled in two independently
|
|
291
|
+
moving layers. It is distorted by waves and attenuated by water depth,
|
|
292
|
+
receiver angle, and the active water region. This is the shimmering light
|
|
293
|
+
commonly mistaken for a floor reflection; it is not mirror reflection.
|
|
294
|
+
|
|
295
|
+
Full-screen underwater fog, image distortion, waterline meniscus, and sun
|
|
296
|
+
shafts remain scene/post-processing responsibilities rather than surface
|
|
297
|
+
material features. The Water Lab changes scene fog and background below the
|
|
298
|
+
surface, but the water module does not force those artistic choices on every
|
|
299
|
+
host application.
|
|
300
|
+
|
|
301
|
+
Scene shadowing and cloud shadows: the surface darkens under cast shadows
|
|
302
|
+
(`sceneShadowStrength`) and shares the global cloud-shadow field
|
|
303
|
+
(`water.setCloudShadow({ strength, coverage, scale, velocity })`) with
|
|
304
|
+
grass, trees, and the environment shader.
|
|
305
|
+
|
|
306
|
+
## CPU/GPU spectrum mirror
|
|
307
|
+
|
|
308
|
+
`buildGerstnerWaves(settings)` builds the 8-component Gerstner spectrum that
|
|
309
|
+
both the vertex shader and the CPU sampler consume;
|
|
310
|
+
`sampleGerstnerHeight(waves, x, z, time)` (wrapped by
|
|
311
|
+
`water.getHeightAt(x, z)`) evaluates the exact same math for buoyancy,
|
|
312
|
+
swimming, and interaction tests. The spectrum constants (wavelength falloff,
|
|
313
|
+
slope limit, gravity) are deliberately not settings because the two sides
|
|
314
|
+
must stay in lockstep — see
|
|
315
|
+
[shader-constants.md](shader-constants.md#water) for the full list and
|
|
316
|
+
where each lives.
|
|
317
|
+
|
|
318
|
+
On a shoaling surface, `nearshorePhase: true` optionally bakes a static-bed,
|
|
319
|
+
one-way mild-slope/ray approximation for the two dominant swell components.
|
|
320
|
+
It keeps the incident period while shortening and turning those components in
|
|
321
|
+
shallower water, and CPU height queries sample the same field. The shader's
|
|
322
|
+
analytic macro normal uses the corresponding baked phase gradient. This
|
|
323
|
+
is intentionally bounded: it does not model diffraction, reflection, a ray
|
|
324
|
+
turning back toward the incident boundary, moving bathymetry, or every detail
|
|
325
|
+
wave. Dedicated breaker-shell mode currently bypasses this phase solve.
|
|
326
|
+
|
|
327
|
+
Swash also has one CPU-authored frame per event. The visible water, gameplay
|
|
328
|
+
queries, and persistent shore-state pass share the same event index, oblique
|
|
329
|
+
edge shape, uprush/backwash progress, and endpoints. That shared frame is what
|
|
330
|
+
prevents foam, wetness, and the moving water edge from becoming separately
|
|
331
|
+
looping animations.
|
|
332
|
+
|
|
333
|
+
## Debug views
|
|
334
|
+
|
|
335
|
+
`?waterDebug=<mode>` in the labs or `water.setDebugMode(mode)`:
|
|
336
|
+
|
|
337
|
+
```text
|
|
338
|
+
depth | foam | normal | ripple | reflection | caustics | specular | fresnel | crest | shoreState
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
`shoreState` displays moisture in red, surface film in green, and the stronger
|
|
342
|
+
of active foam/residue in blue. It is the quickest way to tell whether a visual
|
|
343
|
+
gap is caused by state generation, state/material binding, or final shading.
|
|
344
|
+
|
|
345
|
+
## Technique and research matrix
|
|
346
|
+
|
|
347
|
+
This table separates what the renderer actually implements from useful
|
|
348
|
+
coastal-engineering reference models. The references constrain terminology
|
|
349
|
+
and expected behavior; they are not evidence that this stylized system has
|
|
350
|
+
been physically validated.
|
|
351
|
+
|
|
352
|
+
| Technique | ToonLab status | Scope and important boundary |
|
|
353
|
+
|---|---|---|
|
|
354
|
+
| Eight-component Gerstner spectrum | **Implemented** | Drives open-water geometry and has a CPU mirror for height queries. This is a compact directional spectrum, not an FFT ocean and not a fluid solver. |
|
|
355
|
+
| Finite-depth nearshore phase/refraction | **Implemented, opt-in** | A static-bed, one-way mild-slope/ray bake affects the dominant swell pair. It approximates wavelength shortening and refraction but omits diffraction, reflection, turning rays, moving beds, and the six detail waves. The physical phenomena and numerical methods are covered in the [USACE Coastal Engineering Manual, Part II](https://www.publications.usace.army.mil/Portals/76/Publications/EngineerManuals/EM_1110-2-1100_Part-02.pdf). |
|
|
356
|
+
| Shoaling and breaking | **Implemented, stylized** | Bed depth attenuates/amplifies the authored swell and can drive a separate curling-breaker shell. It is not an energy-conserving Boussinesq, shallow-water, or two-phase breaking calculation; dedicated breaker-shell mode currently bypasses the nearshore phase bake. See the [USACE Coastal Engineering Manual, Part II](https://www.publications.usace.army.mil/Portals/76/Publications/EngineerManuals/EM_1110-2-1100_Part-02.pdf) for the engineering treatment of wave transformations and surf-zone processes. |
|
|
357
|
+
| Swash run-up and backwash | **Implemented, stylized** | A continuous event state gives fast uprush, slower return, 80–100% peak variation, correlated sets, oblique macro-shape, and endpoint handoff. `runupDistance` is a horizontal art/calibration control, not the `R2%` elevation predicted by an empirical model. [Carrier & Greenspan (1958)](https://www.cambridge.org/core/journals/journal-of-fluid-mechanics/article/abs/water-waves-of-finite-amplitude-on-a-sloping-beach/9628CB59A4761A52C12E098ACCE3F1C6) derive nonlinear shallow-water run-up on a plane slope; [Stockdon et al. (2006)](https://pubs.usgs.gov/publication/70030520) show observed run-up depends on setup plus incident- and infragravity-band swash and on offshore conditions and beach properties. ToonLab does not solve or fit either model. |
|
|
358
|
+
| Persistent foam, film, and wet sand | **Implemented, opt-in** | A world-anchored RGBA state atlas is shared by water and ground materials. It preserves visual history but does not simulate bubbles, air entrainment, sediment transport, infiltration, or two-phase flow. |
|
|
359
|
+
| Interactive ripple heightfield | **Implemented, local** | A camera-following 2D GPU heightfield handles splashes and wakes. It is not mass/momentum conserving, is not coupled to the offshore spectrum, and is not a wetting/drying shoreline solver. |
|
|
360
|
+
| Flow maps / spatial currents | **Foundation implemented; authored input required** | `WaterCurrentField` stores authored XZ velocity and an optional domain/obstacle mask for gameplay and shore-foam advection. Its signed-distance projection discourages bank penetration but cannot infer pressure, circulation, wakes, separation, rip currents, or turbulence. Those patterns still need authored/baked vectors or a solver upstream. |
|
|
361
|
+
| Shallow-water equations (SWE) | **Not implemented** | There is no depth-and-momentum time integration, conservative wetting/drying front, obstacle-coupled flow, or numerical run-up solution. The current swash and ripple systems must not be described as SWE. [Carrier & Greenspan (1958)](https://www.cambridge.org/core/journals/journal-of-fluid-mechanics/article/abs/water-waves-of-finite-amplitude-on-a-sloping-beach/9628CB59A4761A52C12E098ACCE3F1C6) is a useful primary reference for the nonlinear shallow-water model class on a plane beach. |
|
|
362
|
+
| FFT spectral ocean | **Not implemented** | No frequency-domain spectrum is transformed into a displacement field. FFT could be a future open-ocean option, but it would not by itself supply beach wetting/drying, swash history, or obstacle-aware currents. |
|
|
363
|
+
| Full CFD / two-phase foam | **Not implemented** | Spray droplets and foam are rendering/simulation effects, not Navier–Stokes water/air volume fractions. |
|
|
364
|
+
|
|
365
|
+
### Research references
|
|
366
|
+
|
|
367
|
+
- G. F. Carrier and H. P. Greenspan, [“Water waves of finite amplitude on a
|
|
368
|
+
sloping beach”](https://www.cambridge.org/core/journals/journal-of-fluid-mechanics/article/abs/water-waves-of-finite-amplitude-on-a-sloping-beach/9628CB59A4761A52C12E098ACCE3F1C6),
|
|
369
|
+
*Journal of Fluid Mechanics* 4(1), 1958. Primary analytic nonlinear
|
|
370
|
+
shallow-water treatment of shoreline motion on a plane slope.
|
|
371
|
+
- H. F. Stockdon, R. A. Holman, P. A. Howd, and A. H. Sallenger Jr.,
|
|
372
|
+
[“Empirical parameterization of setup, swash, and
|
|
373
|
+
runup”](https://pubs.usgs.gov/publication/70030520), *Coastal Engineering*
|
|
374
|
+
53(7), 2006. Primary field-data study separating setup, incident swash, and
|
|
375
|
+
infragravity swash in run-up estimates.
|
|
376
|
+
- U.S. Army Corps of Engineers, [*Coastal Engineering Manual, Part
|
|
377
|
+
II*](https://www.publications.usace.army.mil/Portals/76/Publications/EngineerManuals/EM_1110-2-1100_Part-02.pdf),
|
|
378
|
+
EM 1110-2-1100, 2002. Official engineering reference for wave mechanics,
|
|
379
|
+
transformations, breaking, and surf-zone processes.
|
|
380
|
+
|
|
381
|
+
## Water Lab visual acceptance gates
|
|
382
|
+
|
|
383
|
+
These are regression targets derived from the reported Water Lab captures,
|
|
384
|
+
not a claim that every current build already passes. Use **Ground → Beach
|
|
385
|
+
(swash)**, a 20 m test beach, `runupDistance: 10`, and a camera that can inspect
|
|
386
|
+
the shoreline both alongshore and from above. Observe at least eight complete
|
|
387
|
+
events; a single attractive still does not prove continuity.
|
|
388
|
+
|
|
389
|
+
- **Reach and continuity:** centerline inland maxima fall between 8 m and
|
|
390
|
+
10 m, consecutive events visibly differ, and the edge returns continuously
|
|
391
|
+
to an event-dependent rundown endpoint. No teleport/reset is allowed when a
|
|
392
|
+
cycle changes.
|
|
393
|
+
- **Connected, oblique edge:** the water/sand silhouette remains one connected
|
|
394
|
+
front with an incidence angle, broad tongues, smaller scallops, and real
|
|
395
|
+
gaps. It must not become a ruler-straight or clamped plateau, release one
|
|
396
|
+
mesh column/block at a time, or repeat the same outline every cycle.
|
|
397
|
+
- **Foam belongs to the edge:** the strongest fresh foam intersects the actual
|
|
398
|
+
wet/dry silhouette on both water and sand sides. It may tear into patches and
|
|
399
|
+
leave residue, but a detached interior white strip that looks like a
|
|
400
|
+
reflection or a second looping system is a failure.
|
|
401
|
+
- **Shape variety:** compare several cycles at the same camera. Broad tongue
|
|
402
|
+
widths, holes, breaks, and alongshore positions change while remaining
|
|
403
|
+
spatially coherent; avoid evenly spaced teeth or nearly uniform ribbons.
|
|
404
|
+
- **One optical system:** shallow color, opacity, refraction, and caustic
|
|
405
|
+
motion transition into the swash film without a sharp internal handoff.
|
|
406
|
+
Caustics may fade as the film becomes physically thin and foam may occlude
|
|
407
|
+
them, but they must not stop at a fixed line inside connected water.
|
|
408
|
+
- **Retreat history:** active foam thins into residue, recently exposed sand
|
|
409
|
+
stays darker and briefly glossier, and those marks decay on their configured
|
|
410
|
+
lifetimes instead of disappearing at the procedural cycle boundary.
|
|
411
|
+
- **Coverage:** supported orbit, pan, and zoom views do not reveal a rectangular
|
|
412
|
+
water-tile edge, skirt gap, or finite patch against the horizon. Either the
|
|
413
|
+
water covers the view or scene composition hides its boundary.
|
|
414
|
+
- **Ground switching and controls:** after switching into Beach (swash), orbit,
|
|
415
|
+
pan, and zoom remain responsive; a rendered scene followed by a multi-second
|
|
416
|
+
input freeze is a failure.
|
|
417
|
+
- **Backend safety:** native WebGPU and the WebGL2 fallback retain displaced
|
|
418
|
+
waves and swash. Neither may log a WebGPU private-address-space pipeline
|
|
419
|
+
error or silently fall back to a completely flat surface.
|
|
420
|
+
- **From below:** use the Water Lab's **From below** shortcut. The real sky,
|
|
421
|
+
clouds, and above-water silhouettes remain visible inside the Snell window;
|
|
422
|
+
grazing rays transition into a stylized total-internal-reflection band
|
|
423
|
+
without turning the whole underside transparent.
|
|
424
|
+
- **Seabed:** use the **Seabed** shortcut on open ground. A moving two-layer
|
|
425
|
+
caustic web stays attached to actual receiving geometry, follows the water
|
|
426
|
+
palette and sun tint, fades with depth, and never appears on dry ground.
|
|
427
|
+
- **Debug cross-check:** in `shoreState`, the blue foam signal follows the same
|
|
428
|
+
event as the moving edge, green film trails the retreat, and red moisture
|
|
429
|
+
outlives both. In `caustics` and `foam`, no fixed internal seam should reveal
|
|
430
|
+
two independently phased systems.
|
package/docs/weather.md
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
# Weather system
|
|
2
|
+
|
|
3
|
+
Weather is a separate, reusable world system rather than an environment
|
|
4
|
+
shader preset. The environment cluster still owns how a surface is shaded;
|
|
5
|
+
weather coordinates the temporary state that many systems must share:
|
|
6
|
+
clouds, fog, sun and ambient light, wind, precipitation, lightning, water
|
|
7
|
+
agitation, wetness, snow cover, and ice.
|
|
8
|
+
|
|
9
|
+
Sky Lab owns the reusable baseline sky appearance; Weather owns the current
|
|
10
|
+
condition and only modulates that baseline. Lighting remains authoritative for
|
|
11
|
+
the real directional light and shadow policy. See
|
|
12
|
+
[Lab responsibilities](lab-architecture.md).
|
|
13
|
+
|
|
14
|
+
Open the standalone editor at `/weather-lab/`. It exposes the complete
|
|
15
|
+
schema, smooth preset transitions, a lightning test, local saves, and
|
|
16
|
+
portable JSON import/export.
|
|
17
|
+
|
|
18
|
+
## Composed-world usage
|
|
19
|
+
|
|
20
|
+
`createStylizedWorld` creates the weather coordinator by default with the
|
|
21
|
+
Call Me Sensei preset:
|
|
22
|
+
|
|
23
|
+
```js
|
|
24
|
+
const world = await createStylizedWorld({
|
|
25
|
+
renderer,
|
|
26
|
+
scene,
|
|
27
|
+
camera,
|
|
28
|
+
terrain: { root: terrainRoot, heightAt, size: 1000 },
|
|
29
|
+
water: { level: 0 },
|
|
30
|
+
followTarget: character,
|
|
31
|
+
weather: { preset: 'partlyCloudy', seed: 42 },
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// Smoothly change the whole world, not just the particle effect.
|
|
35
|
+
world.setWeather('thunderstorm', { duration: 5 });
|
|
36
|
+
|
|
37
|
+
// Keep calling the normal composed-world update.
|
|
38
|
+
world.update(delta);
|
|
39
|
+
|
|
40
|
+
// Optional authored day cycle. This binds Sky, Water, the world-owned sun
|
|
41
|
+
// direction, environment materials, and Weather's modulation bridge.
|
|
42
|
+
const lighting = createLightingSystem({ renderer, scene, style: 'call-me-sensei' });
|
|
43
|
+
lighting.attachWorld(world);
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
The coordinator automatically adapts the world sky, sun rig, scene fog,
|
|
47
|
+
ambient lights, cloud shadows, grass, flowers, trees, fauna, ambient VFX,
|
|
48
|
+
and water when those systems are present. `followTarget` centers the GPU
|
|
49
|
+
precipitation window; otherwise it follows the camera.
|
|
50
|
+
|
|
51
|
+
Without a Lighting system, Weather writes the world sun/ambient/fog fallback
|
|
52
|
+
directly. `lighting.attachWorld(world)` calls `weather.setLightingSystem()`
|
|
53
|
+
automatically; from then on Lighting is the sole writer and Weather supplies
|
|
54
|
+
sun tint/intensity, ambient, and fog-color modulation. Detaching Lighting hands
|
|
55
|
+
the same current condition back to Weather without a visible ownership race.
|
|
56
|
+
|
|
57
|
+
Pass `weather: false` for an indoor or fully host-managed scene. Indoor and
|
|
58
|
+
outdoor are not separate weather implementations: a building, cave, or
|
|
59
|
+
vehicle can instead decide how much outside weather reaches its visible
|
|
60
|
+
surfaces and audio. Weather remains one source of truth across transitions
|
|
61
|
+
between spaces.
|
|
62
|
+
|
|
63
|
+
## Built-in conditions
|
|
64
|
+
|
|
65
|
+
There are 22 presets:
|
|
66
|
+
|
|
67
|
+
- Signature and fair: `call_me_sensei`, `clear`, `partlyCloudy`, `cloudy`,
|
|
68
|
+
`overcast`, `windy`
|
|
69
|
+
- Visibility: `haze`, `mist`, `fog`
|
|
70
|
+
- Rain and storms: `drizzle`, `rain`, `heavyRain`, `thunderstorm`,
|
|
71
|
+
`tropicalStorm`
|
|
72
|
+
- Winter: `snow`, `heavySnow`, `blizzard`, `sleet`, `freezingRain`, `hail`
|
|
73
|
+
- Arid: `dustStorm`, `sandstorm`
|
|
74
|
+
|
|
75
|
+
Precipitation is a single instanced draw with static seeded attributes and
|
|
76
|
+
GPU-looped motion. The renderer supports `rain`, `snow`, `sleet`, `hail`,
|
|
77
|
+
and `dust`; preset intensity changes the instance count without per-particle
|
|
78
|
+
CPU updates.
|
|
79
|
+
|
|
80
|
+
## Standalone coordinator and lab adapters
|
|
81
|
+
|
|
82
|
+
Other labs can surface the same weather registry without constructing a
|
|
83
|
+
complete world:
|
|
84
|
+
|
|
85
|
+
```js
|
|
86
|
+
import {
|
|
87
|
+
createWeatherSystem,
|
|
88
|
+
getWeatherPresetOptions,
|
|
89
|
+
} from '@call-me-sensei/toonlab/weather';
|
|
90
|
+
|
|
91
|
+
const weather = createWeatherSystem({
|
|
92
|
+
renderer,
|
|
93
|
+
scene,
|
|
94
|
+
camera,
|
|
95
|
+
followTarget: player,
|
|
96
|
+
groundHeightAt: terrain.heightAt,
|
|
97
|
+
preset: 'snow',
|
|
98
|
+
sky,
|
|
99
|
+
sunRig,
|
|
100
|
+
water,
|
|
101
|
+
grass,
|
|
102
|
+
flowers,
|
|
103
|
+
forest,
|
|
104
|
+
fauna,
|
|
105
|
+
ambientFx,
|
|
106
|
+
// Optional whole-world scene-light adapter supplied by your host. This
|
|
107
|
+
// keeps custom vegetation inputs aligned when Lighting is absent.
|
|
108
|
+
getSun: readSceneSun,
|
|
109
|
+
setSun: applySceneSun,
|
|
110
|
+
// Needed only when an opaque host adapter cannot be inspected. These are
|
|
111
|
+
// the exact values restored by dispose().
|
|
112
|
+
cloudShadowBaseline: currentCloudShadow,
|
|
113
|
+
surfaceBaseline: currentSurfaceState,
|
|
114
|
+
onSurfaceChange: ({ wetness, snowCover, ice }) => {
|
|
115
|
+
terrainMaterial.uniforms.wetness.value = wetness;
|
|
116
|
+
terrainMaterial.uniforms.snowCover.value = snowCover;
|
|
117
|
+
terrainMaterial.uniforms.ice.value = ice;
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
for (const option of getWeatherPresetOptions()) {
|
|
122
|
+
addPresetToLab(option.id, option.label, option.description);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
weather.transitionTo('hail', { duration: 3 });
|
|
126
|
+
weather.update(delta);
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Consumers are optional. A Tree Lab can provide only `forest` and `sky`; a
|
|
130
|
+
Water Lab can provide only `water`; an indoor material lab can consume only
|
|
131
|
+
the normalized surface outputs. This keeps each lab focused while using the
|
|
132
|
+
same preset document and transition semantics.
|
|
133
|
+
|
|
134
|
+
`getSun` / `setSun` form the preferred standalone world-light bridge. The
|
|
135
|
+
state is `{ direction, color, sky }`; Weather captures the getter once, applies
|
|
136
|
+
temporary tint/darkening through the setter, and restores that baseline on
|
|
137
|
+
teardown. `setCloudShadow` and `onSurfaceChange` are write-only adapters, so
|
|
138
|
+
pass `cloudShadowBaseline` / `surfaceBaseline` when their current values are
|
|
139
|
+
not otherwise inspectable. `createStylizedWorld` wires these responsibilities
|
|
140
|
+
for its own systems.
|
|
141
|
+
|
|
142
|
+
## Settings groups
|
|
143
|
+
|
|
144
|
+
`WEATHER_SETTING_FIELD_SCHEMA` is the canonical UI schema. Settings are
|
|
145
|
+
grouped into:
|
|
146
|
+
|
|
147
|
+
- `atmosphere`: cloud coverage/speed/shadows, sky and sun multipliers,
|
|
148
|
+
ambient light, fog color and range
|
|
149
|
+
- `wind`: direction, strength, speed, and vegetation gust controls
|
|
150
|
+
- `precipitation`: type, intensity, appearance, follow volume, speed, and
|
|
151
|
+
particle budget
|
|
152
|
+
- `lightning`: enabled state, strike rate, color, intensity, and duration
|
|
153
|
+
- `surface`: water wave/ripple response plus host-facing wetness, snow, and
|
|
154
|
+
ice targets
|
|
155
|
+
|
|
156
|
+
Weather multiplies the current lower-priority Lighting result, so time of day
|
|
157
|
+
remains independent. For example, `rain` at noon and `rain` at sunset share the
|
|
158
|
+
same condition while retaining their different underlying sun colors and
|
|
159
|
+
angles. On `StylizedSky`, Weather owns a private priority-200 resolver layer;
|
|
160
|
+
on `WaterSurface`, it owns a private layer that adds `waterWaveBoost` over the
|
|
161
|
+
authored wave baseline. `sky.settings` and `water.settings` therefore remain
|
|
162
|
+
portable, while their `renderedSettings` expose the current composition.
|
|
163
|
+
|
|
164
|
+
`weather.dispose()` clears only Weather-owned Sky and Water layers, resets
|
|
165
|
+
Lighting modulation, and restores the captured sun, cloud-shadow, vegetation
|
|
166
|
+
wind, wetness/snow, Ambient FX wind, and host surface baselines. Disposed
|
|
167
|
+
coordinators ignore later refresh/update calls, so teardown cannot resurrect
|
|
168
|
+
stale world state. The visible dome-cloud pattern
|
|
169
|
+
and projected cloud-shadow field are intentionally separate angular/spatial
|
|
170
|
+
procedures; Weather coordinates their condition and motion policy but does not
|
|
171
|
+
claim texel-identical registration.
|
|
172
|
+
|
|
173
|
+
## Lightning, thunder, and surface events
|
|
174
|
+
|
|
175
|
+
Lightning timing is seeded. The visual flash stays inside the renderer;
|
|
176
|
+
thunder is emitted as a delayed host event based on strike distance so a
|
|
177
|
+
game can choose and spatialize its own audio:
|
|
178
|
+
|
|
179
|
+
```js
|
|
180
|
+
weather.addEventListener('lightning', ({ position, distance, thunderDelay }) => {
|
|
181
|
+
spawnBolt(position);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
weather.addEventListener('thunder', ({ distance }) => {
|
|
185
|
+
audio.playThunder({ distance });
|
|
186
|
+
});
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
`weather.root.userData.weatherSurface` always contains the current
|
|
190
|
+
`{ wetness, snowCover, ice, waterWaveBoost, waterRippleRate }` state.
|
|
191
|
+
`onSurfaceChange` is the preferred adapter for custom terrain, prop,
|
|
192
|
+
character, or post-processing materials.
|
|
193
|
+
|
|
194
|
+
## Custom presets
|
|
195
|
+
|
|
196
|
+
Use `createWeatherPresetDocument`, `parseWeatherPresetDocument`, and
|
|
197
|
+
`serializeWeatherPresetDocument` for portable documents. Register an
|
|
198
|
+
imported document with `registerWeatherPresetDocument(document)`; all labs
|
|
199
|
+
that read `getWeatherPresetOptions()` can then expose it without maintaining
|
|
200
|
+
a second preset list.
|