@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
|
@@ -0,0 +1,523 @@
|
|
|
1
|
+
// Persistent shoreline state update pass.
|
|
2
|
+
//
|
|
3
|
+
// Static bed texture (Float32 RGBA):
|
|
4
|
+
// R = world-space bed height, G/B = dHeight/dX / dHeight/dZ,
|
|
5
|
+
// A = valid-domain mask.
|
|
6
|
+
// Persistent state texture (RGBA16F):
|
|
7
|
+
// R = moisture, G = fresh surface film/sheen,
|
|
8
|
+
// B = active advected foam, A = stranded foam residue.
|
|
9
|
+
//
|
|
10
|
+
// This intentionally stays a small fullscreen fragment pass. The main water
|
|
11
|
+
// material only needs to sample the resulting texture once, avoiding more FBM
|
|
12
|
+
// stacks and private variables in the already-dense StylizedWater pipeline.
|
|
13
|
+
|
|
14
|
+
import * as THREE from 'three';
|
|
15
|
+
import {
|
|
16
|
+
abs,
|
|
17
|
+
clamp,
|
|
18
|
+
cos,
|
|
19
|
+
dot,
|
|
20
|
+
exp,
|
|
21
|
+
float,
|
|
22
|
+
Fn,
|
|
23
|
+
If,
|
|
24
|
+
length,
|
|
25
|
+
max,
|
|
26
|
+
min,
|
|
27
|
+
mix,
|
|
28
|
+
positionGeometry,
|
|
29
|
+
select,
|
|
30
|
+
sin,
|
|
31
|
+
smoothstep,
|
|
32
|
+
texture,
|
|
33
|
+
uniform,
|
|
34
|
+
uv,
|
|
35
|
+
vec2,
|
|
36
|
+
vec4,
|
|
37
|
+
} from 'three/tsl';
|
|
38
|
+
import { NodeMaterial } from 'three/webgpu';
|
|
39
|
+
|
|
40
|
+
function hash01(value) {
|
|
41
|
+
const n = Math.sin(value * 127.1 + 311.7) * 43758.5453123;
|
|
42
|
+
return n - Math.floor(n);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function periodicValueNoise(x, y, cells, seed) {
|
|
46
|
+
const gridX = (x / 128) * cells;
|
|
47
|
+
const gridY = (y / 128) * cells;
|
|
48
|
+
const x0 = Math.floor(gridX);
|
|
49
|
+
const y0 = Math.floor(gridY);
|
|
50
|
+
const tx0 = gridX - x0;
|
|
51
|
+
const ty0 = gridY - y0;
|
|
52
|
+
const tx = tx0 * tx0 * (3 - 2 * tx0);
|
|
53
|
+
const ty = ty0 * ty0 * (3 - 2 * ty0);
|
|
54
|
+
const sample = (ix, iy) => {
|
|
55
|
+
const wrappedX = ((ix % cells) + cells) % cells;
|
|
56
|
+
const wrappedY = ((iy % cells) + cells) % cells;
|
|
57
|
+
return hash01(wrappedX * 17.17 + wrappedY * 43.31 + seed);
|
|
58
|
+
};
|
|
59
|
+
const a = THREE.MathUtils.lerp(sample(x0, y0), sample(x0 + 1, y0), tx);
|
|
60
|
+
const b = THREE.MathUtils.lerp(sample(x0, y0 + 1), sample(x0 + 1, y0 + 1), tx);
|
|
61
|
+
return THREE.MathUtils.lerp(a, b, ty);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
let shorelineNoiseTexture = null;
|
|
65
|
+
function getShorelineNoiseTexture() {
|
|
66
|
+
if (shorelineNoiseTexture) return shorelineNoiseTexture;
|
|
67
|
+
const size = 128;
|
|
68
|
+
const data = new Uint8Array(size * size * 4);
|
|
69
|
+
for (let y = 0; y < size; y += 1) {
|
|
70
|
+
for (let x = 0; x < size; x += 1) {
|
|
71
|
+
const index = (y * size + x) * 4;
|
|
72
|
+
const broadA = periodicValueNoise(x, y, 8, 3.17);
|
|
73
|
+
const detailA = periodicValueNoise(x, y, 23, 11.93);
|
|
74
|
+
const broadB = periodicValueNoise(x, y, 11, 29.41);
|
|
75
|
+
const detailB = periodicValueNoise(x, y, 31, 47.77);
|
|
76
|
+
data[index] = Math.round(THREE.MathUtils.clamp(broadA * 0.72 + detailA * 0.28, 0, 1) * 255);
|
|
77
|
+
data[index + 1] = Math.round(THREE.MathUtils.clamp(broadB * 0.7 + detailB * 0.3, 0, 1) * 255);
|
|
78
|
+
data[index + 2] = 0;
|
|
79
|
+
data[index + 3] = 255;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
shorelineNoiseTexture = new THREE.DataTexture(
|
|
83
|
+
data,
|
|
84
|
+
size,
|
|
85
|
+
size,
|
|
86
|
+
THREE.RGBAFormat,
|
|
87
|
+
THREE.UnsignedByteType,
|
|
88
|
+
);
|
|
89
|
+
shorelineNoiseTexture.name = 'waterShoreStateNoise';
|
|
90
|
+
shorelineNoiseTexture.wrapS = THREE.RepeatWrapping;
|
|
91
|
+
shorelineNoiseTexture.wrapT = THREE.RepeatWrapping;
|
|
92
|
+
shorelineNoiseTexture.minFilter = THREE.LinearFilter;
|
|
93
|
+
shorelineNoiseTexture.magFilter = THREE.LinearFilter;
|
|
94
|
+
shorelineNoiseTexture.generateMipmaps = false;
|
|
95
|
+
shorelineNoiseTexture.colorSpace = THREE.NoColorSpace;
|
|
96
|
+
shorelineNoiseTexture.needsUpdate = true;
|
|
97
|
+
return shorelineNoiseTexture;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
let fallbackStateTexture = null;
|
|
101
|
+
function getFallbackStateTexture() {
|
|
102
|
+
if (fallbackStateTexture) return fallbackStateTexture;
|
|
103
|
+
fallbackStateTexture = new THREE.DataTexture(
|
|
104
|
+
new Float32Array([0, 0, 0, 0]),
|
|
105
|
+
1,
|
|
106
|
+
1,
|
|
107
|
+
THREE.RGBAFormat,
|
|
108
|
+
THREE.FloatType,
|
|
109
|
+
);
|
|
110
|
+
fallbackStateTexture.name = 'waterShoreStateFallback';
|
|
111
|
+
fallbackStateTexture.minFilter = THREE.NearestFilter;
|
|
112
|
+
fallbackStateTexture.magFilter = THREE.NearestFilter;
|
|
113
|
+
fallbackStateTexture.generateMipmaps = false;
|
|
114
|
+
fallbackStateTexture.colorSpace = THREE.NoColorSpace;
|
|
115
|
+
fallbackStateTexture.needsUpdate = true;
|
|
116
|
+
return fallbackStateTexture;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
let fallbackBedTexture = null;
|
|
120
|
+
function getFallbackBedTexture() {
|
|
121
|
+
if (fallbackBedTexture) return fallbackBedTexture;
|
|
122
|
+
fallbackBedTexture = new THREE.DataTexture(
|
|
123
|
+
new Float32Array([0, 0, 0, 0]),
|
|
124
|
+
1,
|
|
125
|
+
1,
|
|
126
|
+
THREE.RGBAFormat,
|
|
127
|
+
THREE.FloatType,
|
|
128
|
+
);
|
|
129
|
+
fallbackBedTexture.name = 'waterShoreBedFallback';
|
|
130
|
+
fallbackBedTexture.minFilter = THREE.NearestFilter;
|
|
131
|
+
fallbackBedTexture.magFilter = THREE.NearestFilter;
|
|
132
|
+
fallbackBedTexture.generateMipmaps = false;
|
|
133
|
+
fallbackBedTexture.colorSpace = THREE.NoColorSpace;
|
|
134
|
+
fallbackBedTexture.needsUpdate = true;
|
|
135
|
+
return fallbackBedTexture;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
let fallbackCurrentTexture = null;
|
|
139
|
+
function getFallbackCurrentTexture() {
|
|
140
|
+
if (fallbackCurrentTexture) return fallbackCurrentTexture;
|
|
141
|
+
// Zero signed velocity is encoded at the midpoint. B/A are zero so even a
|
|
142
|
+
// filtered sample cannot contribute when no WaterCurrentField is attached.
|
|
143
|
+
fallbackCurrentTexture = new THREE.DataTexture(
|
|
144
|
+
new Uint8Array([128, 128, 0, 0]),
|
|
145
|
+
1,
|
|
146
|
+
1,
|
|
147
|
+
THREE.RGBAFormat,
|
|
148
|
+
THREE.UnsignedByteType,
|
|
149
|
+
);
|
|
150
|
+
fallbackCurrentTexture.name = 'waterCurrentFieldFallback';
|
|
151
|
+
fallbackCurrentTexture.minFilter = THREE.NearestFilter;
|
|
152
|
+
fallbackCurrentTexture.magFilter = THREE.NearestFilter;
|
|
153
|
+
fallbackCurrentTexture.generateMipmaps = false;
|
|
154
|
+
fallbackCurrentTexture.colorSpace = THREE.NoColorSpace;
|
|
155
|
+
fallbackCurrentTexture.needsUpdate = true;
|
|
156
|
+
return fallbackCurrentTexture;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Fullscreen update material for WaterShoreStateField.
|
|
161
|
+
*
|
|
162
|
+
* `uRegion = (centerX, centerZ, halfWidth, halfDepth)` in world metres.
|
|
163
|
+
* Swash progress/distance are supplied by the CPU so this pass and the visible
|
|
164
|
+
* water can share one event rather than reconstructing separate loop state.
|
|
165
|
+
*/
|
|
166
|
+
export function createWaterShoreStateSimulationNodeMaterial({
|
|
167
|
+
resolutionX = 512,
|
|
168
|
+
resolutionY = 128,
|
|
169
|
+
} = {}) {
|
|
170
|
+
const u = {
|
|
171
|
+
uPrevState: texture(getFallbackStateTexture()),
|
|
172
|
+
uBedMap: texture(getFallbackBedTexture()),
|
|
173
|
+
uNoiseMap: texture(getShorelineNoiseTexture()),
|
|
174
|
+
uCurrentMap: texture(getFallbackCurrentTexture()),
|
|
175
|
+
uTexel: uniform(new THREE.Vector2(1 / resolutionX, 1 / resolutionY)),
|
|
176
|
+
uRegion: uniform(new THREE.Vector4(0, 0, 16, 8)),
|
|
177
|
+
uCurrentRegion: uniform(new THREE.Vector4(0, 0, 1, 1)),
|
|
178
|
+
uUseCurrentMap: uniform(0),
|
|
179
|
+
uCurrentMaxSpeed: uniform(8),
|
|
180
|
+
uCurrentStrength: uniform(1),
|
|
181
|
+
uDelta: uniform(1 / 30),
|
|
182
|
+
uTime: uniform(0),
|
|
183
|
+
uWaterLevel: uniform(0),
|
|
184
|
+
|
|
185
|
+
uSwashEnabled: uniform(0),
|
|
186
|
+
uExplicitRunup: uniform(0),
|
|
187
|
+
uSwashCycle: uniform(0),
|
|
188
|
+
uSwashCycleSpeed: uniform(0),
|
|
189
|
+
uSwashProgress: uniform(0),
|
|
190
|
+
uSwashProgressSpeed: uniform(0),
|
|
191
|
+
uSwashEdgeDistance: uniform(0),
|
|
192
|
+
uSwashEdgeSpeed: uniform(0),
|
|
193
|
+
uPrimaryDirection: uniform(new THREE.Vector2(1, 0)),
|
|
194
|
+
uShorelineRunup: uniform(0.6),
|
|
195
|
+
uWaveEnergy: uniform(0.3),
|
|
196
|
+
uEventNoiseOffset: uniform(new THREE.Vector2()),
|
|
197
|
+
uSwashEdgeShape: uniform(new THREE.Vector3(0, 0.1, 0)),
|
|
198
|
+
|
|
199
|
+
uMoistureWetTime: uniform(0.12),
|
|
200
|
+
uMoistureDryTime: uniform(120),
|
|
201
|
+
uFilmWetTime: uniform(0.08),
|
|
202
|
+
uFilmDryTime: uniform(2),
|
|
203
|
+
uFoamWetLifetime: uniform(6),
|
|
204
|
+
uFoamDryLifetime: uniform(2),
|
|
205
|
+
uResidueLifetime: uniform(12),
|
|
206
|
+
uFoamGain: uniform(7),
|
|
207
|
+
uFoamAdvection: uniform(1),
|
|
208
|
+
uFoamDiffusion: uniform(0.012),
|
|
209
|
+
uFoamAmount: uniform(1),
|
|
210
|
+
uNoiseScale: uniform(0.38),
|
|
211
|
+
uBaseCurrent: uniform(new THREE.Vector2()),
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
const material = new NodeMaterial();
|
|
215
|
+
material.name = 'WaterShoreStateSimulation';
|
|
216
|
+
material.lights = false;
|
|
217
|
+
material.fog = false;
|
|
218
|
+
material.depthTest = false;
|
|
219
|
+
material.depthWrite = false;
|
|
220
|
+
material.vertexNode = Fn(() => vec4(positionGeometry.xy, 0.0, 1.0))();
|
|
221
|
+
|
|
222
|
+
const vUv = uv();
|
|
223
|
+
material.fragmentNode = Fn(() => {
|
|
224
|
+
const result = vec4(0.0).toVar();
|
|
225
|
+
const bed = u.uBedMap.sample(vUv).level(0).toVar();
|
|
226
|
+
|
|
227
|
+
If(bed.a.greaterThan(0.5), () => {
|
|
228
|
+
const worldSize = u.uRegion.zw.mul(2.0).toVar();
|
|
229
|
+
const worldXZ = u.uRegion.xy.add(vUv.sub(0.5).mul(worldSize)).toVar();
|
|
230
|
+
const bedGradient = bed.yz.toVar();
|
|
231
|
+
const gradientLength = length(bedGradient).toVar();
|
|
232
|
+
const bedSlope = max(gradientLength, 0.005).toVar();
|
|
233
|
+
const shoreDirection = bedGradient.div(bedSlope).toVar();
|
|
234
|
+
const alongDirection = vec2(shoreDirection.y, shoreDirection.x.negate()).toVar();
|
|
235
|
+
const alongCoordinate = dot(worldXZ, alongDirection).toVar();
|
|
236
|
+
const restDepth = u.uWaterLevel.sub(bed.r).toVar();
|
|
237
|
+
|
|
238
|
+
// Exact connected-edge shape used by the current visible swash. The CPU
|
|
239
|
+
// owns event progression; this pass only evaluates its spatial offset.
|
|
240
|
+
const progress = clamp(u.uSwashProgress, 0.0, 1.0).toVar();
|
|
241
|
+
const edgeEnvelope = mix(0.18, 1.0, sin(progress.mul(Math.PI))).toVar();
|
|
242
|
+
const envelopeSpeed = cos(progress.mul(Math.PI))
|
|
243
|
+
.mul(Math.PI * 0.82).mul(u.uSwashProgressSpeed).toVar();
|
|
244
|
+
const incidenceSlope = clamp(u.uPrimaryDirection.x.mul(0.52), -0.2, 0.2).toVar();
|
|
245
|
+
const rawEdgeTilt = alongCoordinate.mul(incidenceSlope).negate().toVar();
|
|
246
|
+
const edgeTilt = rawEdgeTilt.div(abs(rawEdgeTilt).div(2.0).add(1.0)).toVar();
|
|
247
|
+
|
|
248
|
+
const scallopA = alongCoordinate.mul(0.32).sub(u.uTime.mul(0.35)).toVar();
|
|
249
|
+
const scallopB = alongCoordinate.mul(0.91).add(u.uTime.mul(0.18)).toVar();
|
|
250
|
+
const edgeScallop = sin(scallopA)
|
|
251
|
+
.sub(sin(u.uTime.mul(-0.35)))
|
|
252
|
+
.add(sin(scallopB).sub(sin(u.uTime.mul(0.18))).mul(0.35))
|
|
253
|
+
.mul(0.4)
|
|
254
|
+
.toVar();
|
|
255
|
+
const scallopSpeed = cos(scallopA).mul(-0.35)
|
|
256
|
+
.add(cos(u.uTime.mul(-0.35)).mul(0.35))
|
|
257
|
+
.add(
|
|
258
|
+
cos(scallopB).mul(0.18)
|
|
259
|
+
.sub(cos(u.uTime.mul(0.18)).mul(0.18))
|
|
260
|
+
.mul(0.35),
|
|
261
|
+
)
|
|
262
|
+
.mul(0.4)
|
|
263
|
+
.toVar();
|
|
264
|
+
const macroAngle = u.uSwashCycle.mul(Math.PI).toVar();
|
|
265
|
+
const macroWave = sin(macroAngle).toVar();
|
|
266
|
+
const macroEnvelope = macroWave.mul(macroWave).toVar();
|
|
267
|
+
const macroEnvelopeSpeed = macroWave.mul(cos(macroAngle))
|
|
268
|
+
.mul(Math.PI * 2).mul(u.uSwashCycleSpeed).toVar();
|
|
269
|
+
const secondaryPhase = u.uSwashEdgeShape.x.mul(-0.71).toVar();
|
|
270
|
+
const macroBase = sin(
|
|
271
|
+
alongCoordinate.mul(u.uSwashEdgeShape.y).add(u.uSwashEdgeShape.x),
|
|
272
|
+
).sub(sin(u.uSwashEdgeShape.x))
|
|
273
|
+
.add(
|
|
274
|
+
sin(
|
|
275
|
+
alongCoordinate.mul(u.uSwashEdgeShape.y).mul(2.35).add(secondaryPhase),
|
|
276
|
+
).sub(sin(secondaryPhase)).mul(0.42),
|
|
277
|
+
)
|
|
278
|
+
.mul(u.uSwashEdgeShape.z)
|
|
279
|
+
.toVar();
|
|
280
|
+
const edgeOffset = edgeTilt.add(edgeScallop).mul(edgeEnvelope)
|
|
281
|
+
.add(macroBase.mul(macroEnvelope)).toVar();
|
|
282
|
+
const edgeOffsetSpeed = scallopSpeed.mul(edgeEnvelope)
|
|
283
|
+
.add(edgeTilt.add(edgeScallop).mul(envelopeSpeed))
|
|
284
|
+
.add(macroBase.mul(macroEnvelopeSpeed))
|
|
285
|
+
.toVar();
|
|
286
|
+
|
|
287
|
+
// In automatic mode the horizontal distance contains 1/slope, so its
|
|
288
|
+
// vertical head reduces to shorelineRunup * energy * progress.
|
|
289
|
+
const automaticHead = u.uShorelineRunup.mul(u.uWaveEnergy).mul(progress).toVar();
|
|
290
|
+
const explicitHead = u.uSwashEdgeDistance.mul(bedSlope).toVar();
|
|
291
|
+
const baseHead = mix(automaticHead, explicitHead, u.uExplicitRunup).toVar();
|
|
292
|
+
const edgeHead = baseHead.add(edgeOffset.mul(bedSlope))
|
|
293
|
+
.mul(u.uSwashEnabled)
|
|
294
|
+
.toVar();
|
|
295
|
+
const filmHead = restDepth.add(edgeHead).toVar();
|
|
296
|
+
const beachMask = smoothstep(0.06, 0.22, restDepth).oneMinus().toVar();
|
|
297
|
+
const wetBehindEdge = smoothstep(-0.004, 0.012, filmHead).toVar();
|
|
298
|
+
const instantWet = mix(1.0, wetBehindEdge, beachMask).toVar();
|
|
299
|
+
const foamWidth = max(bedSlope.mul(0.68), 0.018).toVar();
|
|
300
|
+
|
|
301
|
+
// Moisture and sheen belong to fixed ground coordinates, never to the
|
|
302
|
+
// advected foam sample. Exponential response is update-rate independent.
|
|
303
|
+
const stationaryState = u.uPrevState.sample(vUv).level(0).toVar();
|
|
304
|
+
const moistureTau = select(
|
|
305
|
+
instantWet.greaterThan(stationaryState.r),
|
|
306
|
+
u.uMoistureWetTime,
|
|
307
|
+
u.uMoistureDryTime,
|
|
308
|
+
).toVar();
|
|
309
|
+
const moistureBlend = exp(
|
|
310
|
+
u.uDelta.div(max(moistureTau, 1e-3)).negate(),
|
|
311
|
+
).oneMinus();
|
|
312
|
+
const moisture = mix(stationaryState.r, instantWet, moistureBlend).toVar();
|
|
313
|
+
|
|
314
|
+
const filmTau = select(
|
|
315
|
+
instantWet.greaterThan(stationaryState.g),
|
|
316
|
+
u.uFilmWetTime,
|
|
317
|
+
u.uFilmDryTime,
|
|
318
|
+
).toVar();
|
|
319
|
+
const filmBlend = exp(u.uDelta.div(max(filmTau, 1e-3)).negate()).oneMinus();
|
|
320
|
+
const surfaceFilm = mix(stationaryState.g, instantWet, filmBlend).toVar();
|
|
321
|
+
|
|
322
|
+
// Analytic shore-normal flow follows the same changing edge, including
|
|
323
|
+
// the local scallop velocity. Optional baseCurrent is a future river /
|
|
324
|
+
// channel hook and is deliberately independent of visual UV scrolling.
|
|
325
|
+
const automaticHeadSpeed = u.uShorelineRunup.mul(u.uWaveEnergy)
|
|
326
|
+
.mul(u.uSwashProgressSpeed).toVar();
|
|
327
|
+
const explicitHeadSpeed = u.uSwashEdgeSpeed.mul(bedSlope).toVar();
|
|
328
|
+
const baseHeadSpeed = mix(
|
|
329
|
+
automaticHeadSpeed,
|
|
330
|
+
explicitHeadSpeed,
|
|
331
|
+
u.uExplicitRunup,
|
|
332
|
+
).toVar();
|
|
333
|
+
const normalSpeed = baseHeadSpeed.add(edgeOffsetSpeed.mul(bedSlope))
|
|
334
|
+
.div(bedSlope)
|
|
335
|
+
.mul(u.uSwashEnabled)
|
|
336
|
+
.toVar();
|
|
337
|
+
const clampedNormalSpeed = clamp(normalSpeed, -3.5, 3.5).toVar();
|
|
338
|
+
// Oblique breakers generate a modest longshore current. Carrying rafts
|
|
339
|
+
// sideways prevents successive cycles from stacking foam at identical
|
|
340
|
+
// X positions while remaining tied to real incidence, not random drift.
|
|
341
|
+
const alongshoreSpeed = dot(u.uPrimaryDirection, alongDirection)
|
|
342
|
+
.mul(abs(clampedNormalSpeed)).mul(0.35).toVar();
|
|
343
|
+
// Optional authored world-space flow map. The RGBA8 encoding is shared
|
|
344
|
+
// with WaterCurrentField: signed velocity in RG, fluid weight in B,
|
|
345
|
+
// valid-domain weight in A. It is sampled only in this small transport
|
|
346
|
+
// pass; the main visible-water shader remains unchanged.
|
|
347
|
+
const currentWorldSize = max(u.uCurrentRegion.zw.mul(2.0), vec2(1e-3)).toVar();
|
|
348
|
+
const currentUv = worldXZ.sub(u.uCurrentRegion.xy)
|
|
349
|
+
.div(currentWorldSize).add(0.5).toVar();
|
|
350
|
+
const currentInside = currentUv.x.greaterThanEqual(0.0)
|
|
351
|
+
.and(currentUv.x.lessThanEqual(1.0))
|
|
352
|
+
.and(currentUv.y.greaterThanEqual(0.0))
|
|
353
|
+
.and(currentUv.y.lessThanEqual(1.0));
|
|
354
|
+
const currentInsideMask = select(currentInside, float(1.0), float(0.0)).toVar();
|
|
355
|
+
const encodedCurrent = u.uCurrentMap.sample(clamp(currentUv, 0.0, 1.0))
|
|
356
|
+
.level(0).toVar();
|
|
357
|
+
// WaterCurrentField reserves byte 128 as exact zero. Decode in byte
|
|
358
|
+
// space instead of using `rg * 2 - 1`, whose UNORM midpoint would add
|
|
359
|
+
// a small but persistent preferred drift to still-water foam.
|
|
360
|
+
const spatialCurrent = encodedCurrent.rg.mul(255.0).sub(128.0).div(127.0)
|
|
361
|
+
.mul(u.uCurrentMaxSpeed)
|
|
362
|
+
.mul(encodedCurrent.b.mul(encodedCurrent.a))
|
|
363
|
+
.mul(currentInsideMask)
|
|
364
|
+
.mul(u.uUseCurrentMap)
|
|
365
|
+
.mul(u.uCurrentStrength)
|
|
366
|
+
.toVar();
|
|
367
|
+
const velocity = shoreDirection.mul(clampedNormalSpeed)
|
|
368
|
+
.add(alongDirection.mul(alongshoreSpeed))
|
|
369
|
+
.mul(beachMask).mul(instantWet)
|
|
370
|
+
.add(u.uBaseCurrent.mul(instantWet))
|
|
371
|
+
.add(spatialCurrent.mul(instantWet))
|
|
372
|
+
.mul(u.uFoamAdvection)
|
|
373
|
+
.toVar();
|
|
374
|
+
const backUv = vUv.sub(velocity.mul(u.uDelta).div(max(worldSize, vec2(1e-3))))
|
|
375
|
+
.toVar();
|
|
376
|
+
const backInside = backUv.x.greaterThanEqual(0.0).and(backUv.x.lessThanEqual(1.0))
|
|
377
|
+
.and(backUv.y.greaterThanEqual(0.0)).and(backUv.y.lessThanEqual(1.0));
|
|
378
|
+
const backMask = select(backInside, float(1.0), float(0.0)).toVar();
|
|
379
|
+
const halfTexel = u.uTexel.mul(0.5).toVar();
|
|
380
|
+
const clampedBackUv = clamp(backUv, halfTexel, halfTexel.oneMinus()).toVar();
|
|
381
|
+
const advectedState = u.uPrevState.sample(clampedBackUv).level(0).toVar();
|
|
382
|
+
const advectedFoam = advectedState.b.mul(backMask).toVar();
|
|
383
|
+
|
|
384
|
+
// Small physical-space diffusion disperses rafts without tying the
|
|
385
|
+
// result to atlas resolution. The coefficient is capped to the explicit
|
|
386
|
+
// diffusion stability limit even if callers supply an extreme value.
|
|
387
|
+
const leftFoam = u.uPrevState.sample(
|
|
388
|
+
clamp(clampedBackUv.sub(vec2(u.uTexel.x, 0.0)), halfTexel, halfTexel.oneMinus()),
|
|
389
|
+
).level(0).b.mul(backMask);
|
|
390
|
+
const rightFoam = u.uPrevState.sample(
|
|
391
|
+
clamp(clampedBackUv.add(vec2(u.uTexel.x, 0.0)), halfTexel, halfTexel.oneMinus()),
|
|
392
|
+
).level(0).b.mul(backMask);
|
|
393
|
+
const downFoam = u.uPrevState.sample(
|
|
394
|
+
clamp(clampedBackUv.sub(vec2(0.0, u.uTexel.y)), halfTexel, halfTexel.oneMinus()),
|
|
395
|
+
).level(0).b.mul(backMask);
|
|
396
|
+
const upFoam = u.uPrevState.sample(
|
|
397
|
+
clamp(clampedBackUv.add(vec2(0.0, u.uTexel.y)), halfTexel, halfTexel.oneMinus()),
|
|
398
|
+
).level(0).b.mul(backMask);
|
|
399
|
+
const texelWorld = worldSize.mul(u.uTexel).toVar();
|
|
400
|
+
const invDx2 = float(1.0).div(max(texelWorld.x.mul(texelWorld.x), 1e-6)).toVar();
|
|
401
|
+
const invDz2 = float(1.0).div(max(texelWorld.y.mul(texelWorld.y), 1e-6)).toVar();
|
|
402
|
+
const laplacian = leftFoam.add(rightFoam).sub(advectedFoam.mul(2.0)).mul(invDx2)
|
|
403
|
+
.add(downFoam.add(upFoam).sub(advectedFoam.mul(2.0)).mul(invDz2))
|
|
404
|
+
.toVar();
|
|
405
|
+
const diffusionDt = min(
|
|
406
|
+
u.uFoamDiffusion.mul(u.uDelta),
|
|
407
|
+
float(0.24).div(max(invDx2.add(invDz2), 1e-6)),
|
|
408
|
+
).toVar();
|
|
409
|
+
const dispersedFoam = clamp(advectedFoam.add(laplacian.mul(diffusionDt)), 0.0, 1.0)
|
|
410
|
+
.toVar();
|
|
411
|
+
|
|
412
|
+
// A deterministic, world-anchored source with a per-event offset. There
|
|
413
|
+
// is intentionally no nonzero coverage floor: the front is broken into
|
|
414
|
+
// patches at injection, while temporal state supplies continuity.
|
|
415
|
+
const noiseSample = u.uNoiseMap.sample(
|
|
416
|
+
worldXZ.mul(u.uNoiseScale).add(u.uEventNoiseOffset),
|
|
417
|
+
).level(0).rg.toVar();
|
|
418
|
+
const fineNoise = u.uNoiseMap.sample(
|
|
419
|
+
// A second independent but still resolvable scale supplies holes
|
|
420
|
+
// inside the broad rafts. The former 2.37 multiplier approached the
|
|
421
|
+
// state atlas footprint and read as regular cut-out scallops.
|
|
422
|
+
worldXZ.mul(u.uNoiseScale.mul(1.65))
|
|
423
|
+
.add(u.uEventNoiseOffset.mul(1.71)).add(vec2(0.31, 0.67)),
|
|
424
|
+
).level(0).rg.toVar();
|
|
425
|
+
// The signed film head is also what clips the visible water. Keep the
|
|
426
|
+
// source centered on that physical silhouette and let noise decide
|
|
427
|
+
// whether a raft exists along it. The old +/-0.7*foamWidth cross-shore
|
|
428
|
+
// displacement could move a patch ~48 cm inside a 1:20 beach, leaving
|
|
429
|
+
// a clear strip between sand and white and making foam read as a
|
|
430
|
+
// reflection floating in the sheet.
|
|
431
|
+
const edgeJitter = noiseSample.r.sub(0.5)
|
|
432
|
+
.mul(min(foamWidth.mul(0.22), 0.004)).toVar();
|
|
433
|
+
const sourceHead = filmHead.add(edgeJitter).toVar();
|
|
434
|
+
// Randomize the distance each attached raft reaches into the water,
|
|
435
|
+
// rather than giving every surviving patch the same band width. On the
|
|
436
|
+
// lab's 1:20 beach this spans roughly 0.35-1.7 m and recreates the
|
|
437
|
+
// believable tongues/holes of the earlier foam without detaching them
|
|
438
|
+
// from the silhouette.
|
|
439
|
+
const raftReach = foamWidth
|
|
440
|
+
.mul(mix(0.5, 1.9, noiseSample.g))
|
|
441
|
+
.mul(mix(0.8, 1.25, fineNoise.r))
|
|
442
|
+
.toVar();
|
|
443
|
+
const frontBand = smoothstep(-0.01, -0.001, sourceHead)
|
|
444
|
+
.mul(smoothstep(raftReach.mul(0.55), raftReach, sourceHead).oneMinus())
|
|
445
|
+
.toVar();
|
|
446
|
+
const trailBand = smoothstep(raftReach.mul(0.42), raftReach.mul(0.82), sourceHead)
|
|
447
|
+
.mul(smoothstep(raftReach.mul(1.55), raftReach.mul(2.8), sourceHead).oneMinus())
|
|
448
|
+
.toVar();
|
|
449
|
+
const speed = clamp(abs(clampedNormalSpeed).div(3.5), 0.0, 1.0).toVar();
|
|
450
|
+
const backwash = clamp(clampedNormalSpeed.negate().div(3.5), 0.0, 1.0).toVar();
|
|
451
|
+
const frontSource = frontBand
|
|
452
|
+
.mul(smoothstep(0.35, 0.58, noiseSample.r))
|
|
453
|
+
.mul(smoothstep(0.3, 0.55, fineNoise.g))
|
|
454
|
+
// The moving front injects strongly, but near maximum run-up its
|
|
455
|
+
// speed approaches zero. A large stationary floor used to pile foam
|
|
456
|
+
// into the same crest-shaped stripe for several frames; temporal
|
|
457
|
+
// retention already carries the moving rafts through that pause.
|
|
458
|
+
.mul(mix(0.32, 1.0, speed));
|
|
459
|
+
const trailSource = trailBand
|
|
460
|
+
.mul(smoothstep(0.55, 0.72, noiseSample.g))
|
|
461
|
+
.mul(smoothstep(0.38, 0.6, fineNoise.r))
|
|
462
|
+
.mul(backwash)
|
|
463
|
+
.mul(0.18);
|
|
464
|
+
const source = max(frontSource, trailSource)
|
|
465
|
+
.mul(beachMask)
|
|
466
|
+
.mul(u.uSwashEnabled)
|
|
467
|
+
.mul(clamp(u.uFoamAmount, 0.0, 2.0))
|
|
468
|
+
.toVar();
|
|
469
|
+
|
|
470
|
+
const foamLifetime = mix(u.uFoamDryLifetime, u.uFoamWetLifetime, instantWet).toVar();
|
|
471
|
+
const retainedFoam = dispersedFoam.mul(
|
|
472
|
+
exp(u.uDelta.div(max(foamLifetime, 1e-3)).negate()),
|
|
473
|
+
).toVar();
|
|
474
|
+
const injectedFoam = source.mul(
|
|
475
|
+
exp(u.uFoamGain.mul(u.uDelta).negate()).oneMinus(),
|
|
476
|
+
).toVar();
|
|
477
|
+
// Latch only the already-gated current lip into the visible range. A
|
|
478
|
+
// fast 6-7 m/s uprush can move several state texels during the 2-5
|
|
479
|
+
// frames needed for ordinary temporal accumulation, which left the
|
|
480
|
+
// bright raft visibly behind the water silhouette. Because frontSource
|
|
481
|
+
// has two true zero-floor gates, this adds immediate attached patches
|
|
482
|
+
// without drawing a continuous fallback line.
|
|
483
|
+
const attachedLip = frontSource.mul(0.72).toVar();
|
|
484
|
+
const activeFoam = clamp(
|
|
485
|
+
max(
|
|
486
|
+
retainedFoam.add(injectedFoam.mul(retainedFoam.oneMinus())),
|
|
487
|
+
attachedLip,
|
|
488
|
+
),
|
|
489
|
+
0.0,
|
|
490
|
+
1.0,
|
|
491
|
+
).toVar();
|
|
492
|
+
|
|
493
|
+
// Residue follows foam while covered, then sticks to exposed sand. It
|
|
494
|
+
// fades slowly and washes out gently on the next inundation.
|
|
495
|
+
const advectedResidue = advectedState.a.mul(backMask).toVar();
|
|
496
|
+
const residueBase = mix(stationaryState.a, advectedResidue, instantWet).toVar();
|
|
497
|
+
const residueRetained = residueBase
|
|
498
|
+
.mul(exp(u.uDelta.div(max(u.uResidueLifetime, 1e-3)).negate()))
|
|
499
|
+
.mul(exp(instantWet.mul(u.uDelta).mul(-0.18)))
|
|
500
|
+
.toVar();
|
|
501
|
+
const residueDeposit = activeFoam.mul(beachMask).mul(instantWet.oneMinus())
|
|
502
|
+
.mul(exp(u.uDelta.mul(-2.5)).oneMinus())
|
|
503
|
+
.add(
|
|
504
|
+
trailSource.mul(backwash)
|
|
505
|
+
.mul(exp(u.uDelta.mul(-0.8)).oneMinus()),
|
|
506
|
+
)
|
|
507
|
+
.toVar();
|
|
508
|
+
const residue = clamp(
|
|
509
|
+
residueRetained.add(residueDeposit.mul(residueRetained.oneMinus())),
|
|
510
|
+
0.0,
|
|
511
|
+
1.0,
|
|
512
|
+
).toVar();
|
|
513
|
+
|
|
514
|
+
result.assign(vec4(moisture, surfaceFilm, activeFoam, residue));
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
return result;
|
|
518
|
+
})();
|
|
519
|
+
|
|
520
|
+
material.uniforms = u;
|
|
521
|
+
material.userData.isWaterShoreStateSimulation = true;
|
|
522
|
+
return material;
|
|
523
|
+
}
|