@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.
Files changed (205) hide show
  1. package/AGENTS.md +127 -4
  2. package/ATTRIBUTION.md +41 -3
  3. package/README.md +234 -34
  4. package/docs/characters.md +144 -0
  5. package/docs/debug-panel.md +126 -0
  6. package/docs/docs.css +589 -0
  7. package/docs/environment.md +186 -0
  8. package/docs/getting-started.md +180 -0
  9. package/docs/index.html +14 -0
  10. package/docs/lab-architecture.md +100 -0
  11. package/docs/lighting.md +825 -0
  12. package/docs/main.jsx +739 -0
  13. package/docs/mcp.md +97 -0
  14. package/docs/post-processing.md +98 -0
  15. package/docs/settings-reference.md +1878 -0
  16. package/docs/shader-constants.md +77 -0
  17. package/docs/sky.md +182 -0
  18. package/docs/style-labs.md +309 -0
  19. package/docs/texture-lab.md +135 -0
  20. package/docs/toon-shading.md +183 -0
  21. package/docs/tsl-conventions.md +167 -0
  22. package/docs/vegetation-sky.md +275 -0
  23. package/docs/water.md +430 -0
  24. package/docs/weather.md +200 -0
  25. package/docs/world-scale.md +111 -0
  26. package/mcp/server.mjs +610 -0
  27. package/mcp/style-lab-tools.mjs +371 -0
  28. package/mcp/vite-plugin.mjs +174 -0
  29. package/mcp/workspace.mjs +397 -0
  30. package/package.json +64 -5
  31. package/src/ambientfx/INTEGRATION.md +164 -0
  32. package/src/ambientfx/ambientFxPresets.js +83 -0
  33. package/src/ambientfx/ambientFxSettings.js +368 -0
  34. package/src/ambientfx/emitters.js +169 -0
  35. package/src/ambientfx/index.js +5 -0
  36. package/src/ambientfx/particleBackbone.js +493 -0
  37. package/src/ambientfx/stylizedAmbientFx.js +450 -0
  38. package/src/assetlib/ambientcg.js +117 -0
  39. package/src/assetlib/assetRef.js +91 -0
  40. package/src/assetlib/importedEntry.js +40 -0
  41. package/src/assetlib/index.js +23 -0
  42. package/src/assetlib/kaykit.js +192 -0
  43. package/src/assetlib/kaykitStaticIndex.js +700 -0
  44. package/src/assetlib/loadImported.js +143 -0
  45. package/src/assetlib/opensource3d.js +113 -0
  46. package/src/assetlib/polyhaven.js +182 -0
  47. package/src/assetlib/polypizza.js +115 -0
  48. package/src/assetlib/smithsonian.js +214 -0
  49. package/src/assetlib/sources.js +279 -0
  50. package/src/assetlib/zip.js +58 -0
  51. package/src/biome/biomeGenerator.js +385 -0
  52. package/src/biome/biomeRuntime.js +299 -0
  53. package/src/biome/index.js +2 -0
  54. package/src/buildinggen/buildingAsset.js +44 -0
  55. package/src/buildinggen/buildingGrammar.js +311 -0
  56. package/src/buildinggen/buildingMesh.js +451 -0
  57. package/src/buildinggen/buildingPresets.js +46 -0
  58. package/src/buildinggen/buildingRecipe.js +100 -0
  59. package/src/buildinggen/buildingSettings.js +238 -0
  60. package/src/buildinggen/index.js +6 -0
  61. package/src/camera/cameraDirector.js +157 -0
  62. package/src/camera/cameraGenerator.js +367 -0
  63. package/src/camera/cameraRig.js +570 -0
  64. package/src/camera/cameraSettings.js +236 -0
  65. package/src/camera/index.js +7 -0
  66. package/src/catalog/builtinEntries.js +245 -0
  67. package/src/catalog/catalog.js +210 -0
  68. package/src/catalog/index.js +3 -0
  69. package/src/catalog/manifest.js +84 -0
  70. package/src/core/generation.js +529 -0
  71. package/src/environment/environmentRigs.js +21 -1
  72. package/src/environment/environmentSettings.js +4 -0
  73. package/src/environment/environmentSunShadowPass.js +8 -0
  74. package/src/fauna/INTEGRATION.md +174 -0
  75. package/src/fauna/boids.js +861 -0
  76. package/src/fauna/faunaBodies.js +492 -0
  77. package/src/fauna/faunaPresets.js +52 -0
  78. package/src/fauna/faunaSettings.js +525 -0
  79. package/src/fauna/index.js +5 -0
  80. package/src/fauna/stylizedFauna.js +395 -0
  81. package/src/game-feel/gameFeelGenerator.js +402 -0
  82. package/src/game-feel/gameFeelRuntime.js +549 -0
  83. package/src/game-feel/index.js +2 -0
  84. package/src/index.js +17 -6
  85. package/src/lighting/colorIntensity.js +177 -0
  86. package/src/lighting/index.js +161 -0
  87. package/src/lighting/lightDescriptors.js +249 -0
  88. package/src/lighting/lightingCapabilities.js +79 -0
  89. package/src/lighting/lightingDocuments.js +247 -0
  90. package/src/lighting/lightingFixtures.js +446 -0
  91. package/src/lighting/lightingGenerator.js +449 -0
  92. package/src/lighting/lightingPresets.js +319 -0
  93. package/src/lighting/lightingRuntime.js +723 -0
  94. package/src/lighting/lightingStyle.js +386 -0
  95. package/src/lighting/lightingSystem.js +774 -0
  96. package/src/lighting/unrealExport.js +186 -0
  97. package/src/lighting/utils.js +87 -0
  98. package/src/motion/index.js +5 -0
  99. package/src/motion/motionClip.js +441 -0
  100. package/src/motion/motionController.js +628 -0
  101. package/src/motion/motionDocuments.js +225 -0
  102. package/src/motion/motionGraph.js +307 -0
  103. package/src/motion/motionSettings.js +222 -0
  104. package/src/pathgen/index.js +7 -0
  105. package/src/pathgen/pathBridge.js +232 -0
  106. package/src/pathgen/pathPresets.js +35 -0
  107. package/src/pathgen/pathRibbon.js +410 -0
  108. package/src/pathgen/pathRouter.js +380 -0
  109. package/src/pathgen/pathSettings.js +335 -0
  110. package/src/pathgen/pathTextures.js +123 -0
  111. package/src/pathgen/stylizedPaths.js +453 -0
  112. package/src/post/index.js +1 -0
  113. package/src/post/postGenerator.js +177 -0
  114. package/src/post/postProcessing.js +41 -0
  115. package/src/propgen/generatorsWave1.js +379 -0
  116. package/src/propgen/generatorsWave2.js +462 -0
  117. package/src/propgen/index.js +5 -0
  118. package/src/propgen/propAsset.js +323 -0
  119. package/src/propgen/propParts.js +170 -0
  120. package/src/propgen/propPlacement.js +459 -0
  121. package/src/propgen/propPresets.js +82 -0
  122. package/src/propgen/propSettings.js +395 -0
  123. package/src/shaders-tsl/chunks/projected-water-caustics.js +242 -0
  124. package/src/shaders-tsl/chunks/vegetation-style.js +360 -0
  125. package/src/shaders-tsl/chunks/water-shore-state.js +31 -0
  126. package/src/shaders-tsl/chunks/water-waves.js +90 -11
  127. package/src/shaders-tsl/environment.js +15 -1
  128. package/src/shaders-tsl/flower.js +279 -30
  129. package/src/shaders-tsl/grass.js +60 -33
  130. package/src/shaders-tsl/sky.js +125 -31
  131. package/src/shaders-tsl/tree-leaf.js +61 -25
  132. package/src/shaders-tsl/water-breaker.js +7 -4
  133. package/src/shaders-tsl/water-shore-state-simulation.js +523 -0
  134. package/src/shaders-tsl/water.js +439 -49
  135. package/src/shaders-tsl/woody-surface.js +154 -0
  136. package/src/sky/sceneOverrideLayers.js +10 -0
  137. package/src/sky/skyQuality.js +26 -0
  138. package/src/sky/stylizedSky.js +753 -45
  139. package/src/soundscape/index.js +4 -0
  140. package/src/soundscape/soundscapeGenerator.js +179 -0
  141. package/src/soundscape/soundscapeRuntime.js +806 -0
  142. package/src/soundscape/soundscapeSettings.js +292 -0
  143. package/src/styles/index.js +13 -0
  144. package/src/styles/styleBundle.js +325 -0
  145. package/src/stylizedTerrain.js +32 -2
  146. package/src/stylizedWorld.js +423 -20
  147. package/src/texgen/evaluateTexture.js +675 -0
  148. package/src/texgen/index.js +60 -0
  149. package/src/texgen/noise2.js +210 -0
  150. package/src/texgen/textureAi.js +436 -0
  151. package/src/texgen/textureGenerators.js +516 -0
  152. package/src/texgen/texturePresets.js +490 -0
  153. package/src/texgen/textureSettings.js +342 -0
  154. package/src/texgen/textureThree.js +59 -0
  155. package/src/vegetation/flowerSpecies.js +15 -3
  156. package/src/vegetation/grassPalettes.js +153 -0
  157. package/src/vegetation/index.js +6 -0
  158. package/src/vegetation/stylizedBush.js +2 -0
  159. package/src/vegetation/stylizedFlower.js +82 -0
  160. package/src/vegetation/stylizedFlowers.js +48 -7
  161. package/src/vegetation/stylizedForest.js +29 -1
  162. package/src/vegetation/stylizedGrass.js +291 -56
  163. package/src/vegetation/stylizedTree.js +38 -2
  164. package/src/vegetation/stylizedTreeFoliage.js +2 -1
  165. package/src/vegetation/vegetationShaders.js +1110 -0
  166. package/src/vfxgen/INTEGRATION.md +145 -0
  167. package/src/vfxgen/core/burstBackbone.js +380 -0
  168. package/src/vfxgen/core/projectileCore.js +92 -0
  169. package/src/vfxgen/core/spriteShapes.js +98 -0
  170. package/src/vfxgen/core/trailRibbon.js +272 -0
  171. package/src/vfxgen/core/vfxRandom.js +29 -0
  172. package/src/vfxgen/effects/emitHelpers.js +37 -0
  173. package/src/vfxgen/effects/magicEffects.js +162 -0
  174. package/src/vfxgen/effects/movementEffects.js +87 -0
  175. package/src/vfxgen/effects/weaponEffects.js +118 -0
  176. package/src/vfxgen/index.js +18 -0
  177. package/src/vfxgen/moves/moveController.js +146 -0
  178. package/src/vfxgen/moves/moveLibrary.js +335 -0
  179. package/src/vfxgen/vfxPresets.js +98 -0
  180. package/src/vfxgen/vfxSettings.js +384 -0
  181. package/src/vfxgen/vfxSystem.js +449 -0
  182. package/src/vfxgen/weapons/stylizedWeapons.js +137 -0
  183. package/src/villagegen/index.js +4 -0
  184. package/src/villagegen/stylizedVillage.js +490 -0
  185. package/src/villagegen/villageArchetypes.js +160 -0
  186. package/src/villagegen/villageNames.js +40 -0
  187. package/src/villagegen/villageSites.js +105 -0
  188. package/src/water/sceneOverrideLayers.js +23 -0
  189. package/src/water/water.js +5 -0
  190. package/src/water/waterBreakerSystem.js +15 -1
  191. package/src/water/waterCurrentField.js +447 -0
  192. package/src/water/waterMaterial.js +12 -0
  193. package/src/water/waterNearshorePhase.js +320 -0
  194. package/src/water/waterScenePasses.js +83 -30
  195. package/src/water/waterSettings.js +325 -13
  196. package/src/water/waterShoreMaterial.js +322 -0
  197. package/src/water/waterShoreStateField.js +605 -0
  198. package/src/water/waterSurface.js +797 -28
  199. package/src/weather/index.js +6 -0
  200. package/src/weather/weatherPrecipitation.js +221 -0
  201. package/src/weather/weatherPresets.js +258 -0
  202. package/src/weather/weatherSettings.js +269 -0
  203. package/src/weather/weatherSystem.js +871 -0
  204. package/src/worldMinimap.js +62 -0
  205. package/src/worldPresets.js +4 -1
@@ -0,0 +1,516 @@
1
+ // Generator catalog for the procedural texture engine. Every generator
2
+ // compiles a layer spec into a sampler `(u, v) -> { v, cell }` where u/v
3
+ // are tile coordinates in [0, 1), v is a scalar field in [0, 1], and cell
4
+ // is a stable uint32 id when the generator has discrete cells (bricks,
5
+ // tiles, worley) — null otherwise. All samplers tile exactly: noise wraps
6
+ // its lattice (see noise2.js) and patterns are periodic by construction.
7
+ //
8
+ // A layer spec carries the union of parameters; each generator declares
9
+ // `uses` so UIs can show only the sliders that matter. Params:
10
+ // scale, detail, detailGain lattice noise frequency / octaves / gain
11
+ // stretchX, stretchY anisotropy (periods stay integer)
12
+ // columns, rows pattern grid
13
+ // gap, bevel pattern mortar width + edge ramp
14
+ // cellJitter worley jitter / dot scatter
15
+ // cellVariation per-cell value variance
16
+ // edgeWidth crack/caustic line width
17
+ // rings, grain wood rings + streaks / marble veins
18
+ // warp, warpScale domain warp amount + frequency
19
+
20
+ import { hash3u, hashCombine } from '../rockgen/noise/prng.js';
21
+ import {
22
+ periodicBillow2,
23
+ periodicCellular2,
24
+ periodicFbm2,
25
+ periodicPerlin2,
26
+ periodicRidged2,
27
+ periodicTurbulence2,
28
+ periodicValue2,
29
+ periodicWarp2,
30
+ } from './noise2.js';
31
+
32
+ const clamp01 = (v) => (v < 0 ? 0 : v > 1 ? 1 : v);
33
+
34
+ function smoothstep(edge0, edge1, x) {
35
+ if (edge0 === edge1) return x < edge0 ? 0 : 1;
36
+ const t = clamp01((x - edge0) / (edge1 - edge0));
37
+ return t * t * (3 - 2 * t);
38
+ }
39
+
40
+ /** Fractional part that stays positive. */
41
+ function fract(v) {
42
+ return v - Math.floor(v);
43
+ }
44
+
45
+ /** Integer periods from scale * stretch, floored at 1 to keep tiling exact. */
46
+ function periods(params) {
47
+ const scale = Math.max(1, params.scale ?? 6);
48
+ return {
49
+ px: Math.max(1, Math.round(scale * (params.stretchX ?? 1))),
50
+ py: Math.max(1, Math.round(scale * (params.stretchY ?? 1))),
51
+ };
52
+ }
53
+
54
+ function intIn(value, min, max, fallback) {
55
+ const v = Math.round(Number(value));
56
+ if (!Number.isFinite(v)) return fallback;
57
+ return Math.min(max, Math.max(min, v));
58
+ }
59
+
60
+ /**
61
+ * Shared domain warp: returns a function mapping (u, v) -> warped (u, v),
62
+ * still periodic in the unit tile. `warp` is in fractions of a warp cell.
63
+ */
64
+ function compileWarp(params, seed) {
65
+ const amount = params.warp ?? 0;
66
+ if (!(amount > 0)) return null;
67
+ const wp = intIn(params.warpScale ?? 3, 1, 32, 3);
68
+ const warpSeed = hashCombine(seed, 0x77a5);
69
+ const strength = amount * 0.6 / wp;
70
+ return (u, v) => {
71
+ const { wx, wy } = periodicWarp2(warpSeed, u * wp, v * wp, wp, wp, 2);
72
+ return { u: u + wx * strength, v: v + wy * strength };
73
+ };
74
+ }
75
+
76
+ /** Per-cell random in [0, 1) from a cell hash id. */
77
+ function cellRandom(id, salt) {
78
+ return hash3u(salt, id | 0, (id >>> 16) | 0, 0) / 4294967296;
79
+ }
80
+
81
+ /** Applies per-cell value variance inside the tile face (mask-weighted). */
82
+ function withCellVariation(value, id, amount, seed) {
83
+ if (!(amount > 0)) return value;
84
+ const r = cellRandom(id, hashCombine(seed, 0x51ce));
85
+ return clamp01(value + (r - 0.5) * amount * 0.9 * Math.min(1, value * 2));
86
+ }
87
+
88
+ // ---------------------------------------------------------------------------
89
+ // Noise family
90
+ // ---------------------------------------------------------------------------
91
+
92
+ function makeNoise(evalNoise, signed) {
93
+ return (params, seed) => {
94
+ const { px, py } = periods(params);
95
+ const octaves = intIn(params.detail ?? 4, 1, 8, 4);
96
+ const gain = Math.min(0.85, Math.max(0.15, params.detailGain ?? 0.5));
97
+ return (u, v) => {
98
+ const n = evalNoise(seed, u * px, v * py, px, py, octaves, gain);
99
+ return { v: clamp01(signed ? n * 0.5 + 0.5 : n), cell: null };
100
+ };
101
+ };
102
+ }
103
+
104
+ // ---------------------------------------------------------------------------
105
+ // Cellular family
106
+ // ---------------------------------------------------------------------------
107
+
108
+ function makeCellular(project) {
109
+ return (params, seed) => {
110
+ const { px, py } = periods(params);
111
+ const jitter = clamp01(params.cellJitter ?? 1);
112
+ const edgeWidth = Math.min(0.6, Math.max(0.01, params.edgeWidth ?? 0.12));
113
+ const variation = clamp01(params.cellVariation ?? 0);
114
+ return (u, v) => {
115
+ const c = periodicCellular2(seed, u * px, v * py, px, py, jitter);
116
+ const value = project(c, edgeWidth);
117
+ return { v: withCellVariation(clamp01(value), c.id, variation, seed), cell: c.id };
118
+ };
119
+ };
120
+ }
121
+
122
+ // ---------------------------------------------------------------------------
123
+ // Grid patterns (bricks, tiles, checker, weave, ...)
124
+ // ---------------------------------------------------------------------------
125
+
126
+ /** Distance-to-edge bevel profile for rectangular cells. */
127
+ function faceProfile(lu, lv, gap, bevel) {
128
+ const d = Math.min(lu, 1 - lu, lv, 1 - lv);
129
+ const g = gap * 0.5;
130
+ return smoothstep(g, g + Math.max(0.004, bevel), d);
131
+ }
132
+
133
+ function compileBricks(params, seed, offsetMode) {
134
+ const cols = intIn(params.columns ?? 4, 1, 64, 4);
135
+ const rows = intIn(params.rows ?? 8, 1, 64, 8);
136
+ const gap = Math.min(0.4, Math.max(0, params.gap ?? 0.06));
137
+ const bevel = Math.min(0.5, Math.max(0, params.bevel ?? 0.12));
138
+ const variation = clamp01(params.cellVariation ?? 0.35);
139
+ return (u, v) => {
140
+ const y = v * rows;
141
+ const row = Math.floor(y);
142
+ const shift = offsetMode === 'running' ? (row & 1) * 0.5 : 0;
143
+ const x = u * cols + shift;
144
+ const col = Math.floor(x);
145
+ const id = hash3u(seed, ((col % cols) + cols) % cols, ((row % rows) + rows) % rows, 0);
146
+ const value = faceProfile(fract(x), fract(y), gap, bevel);
147
+ return { v: withCellVariation(value, id, variation, seed), cell: id };
148
+ };
149
+ }
150
+
151
+ function compileChecker(params, seed) {
152
+ const cols = intIn(params.columns ?? 4, 1, 64, 4);
153
+ const rows = intIn(params.rows ?? 4, 1, 64, 4);
154
+ return (u, v) => {
155
+ const x = Math.floor(u * cols);
156
+ const y = Math.floor(v * rows);
157
+ const id = hash3u(seed, ((x % cols) + cols) % cols, ((y % rows) + rows) % rows, 0);
158
+ return { v: (x + y) & 1 ? 1 : 0, cell: id };
159
+ };
160
+ }
161
+
162
+ function compileGrid(params) {
163
+ const cols = intIn(params.columns ?? 8, 1, 64, 8);
164
+ const rows = intIn(params.rows ?? 8, 1, 64, 8);
165
+ const gap = Math.min(0.4, Math.max(0.005, params.gap ?? 0.06));
166
+ const bevel = Math.min(0.5, Math.max(0.004, params.bevel ?? 0.06));
167
+ return (u, v) => {
168
+ const lu = fract(u * cols);
169
+ const lv = fract(v * rows);
170
+ const d = Math.min(lu, 1 - lu, lv, 1 - lv);
171
+ return { v: smoothstep(gap * 0.5, gap * 0.5 + bevel, d), cell: null };
172
+ };
173
+ }
174
+
175
+ function compileStripes(params) {
176
+ const cols = intIn(params.columns ?? 8, 1, 64, 8);
177
+ return (u) => {
178
+ const t = fract(u * cols);
179
+ return { v: 1 - Math.abs(t * 2 - 1), cell: null };
180
+ };
181
+ }
182
+
183
+ function compileChevron(params) {
184
+ const cols = intIn(params.columns ?? 8, 1, 64, 8);
185
+ const rows = intIn(params.rows ?? 8, 1, 64, 8);
186
+ return (u, v) => {
187
+ const zig = Math.abs(fract(v * rows) * 2 - 1);
188
+ const t = fract(u * cols + zig);
189
+ return { v: 1 - Math.abs(t * 2 - 1), cell: null };
190
+ };
191
+ }
192
+
193
+ function compileWeave(params, seed) {
194
+ const cols = intIn(params.columns ?? 10, 1, 64, 10);
195
+ const rows = intIn(params.rows ?? 10, 1, 64, 10);
196
+ const gap = Math.min(0.45, Math.max(0.02, params.gap ?? 0.16));
197
+ return (u, v) => {
198
+ const x = u * cols;
199
+ const y = v * rows;
200
+ const ix = Math.floor(x);
201
+ const iy = Math.floor(y);
202
+ const lx = fract(x);
203
+ const ly = fract(y);
204
+ // Rounded thread profiles; parity picks which thread crosses on top.
205
+ const tx = 1 - Math.abs(lx * 2 - 1);
206
+ const ty = 1 - Math.abs(ly * 2 - 1);
207
+ const px = smoothstep(gap, gap + 0.35, tx);
208
+ const py = smoothstep(gap, gap + 0.35, ty);
209
+ const over = (ix + iy) & 1;
210
+ const id = hash3u(seed, ((ix % cols) + cols) % cols, ((iy % rows) + rows) % rows, 0);
211
+ const top = over ? px * (0.72 + 0.28 * ty) : py * (0.72 + 0.28 * tx);
212
+ const under = over ? py * 0.5 : px * 0.5;
213
+ return { v: clamp01(Math.max(top, under)), cell: id };
214
+ };
215
+ }
216
+
217
+ function compileBasketWeave(params, seed) {
218
+ const cols = intIn(params.columns ?? 6, 1, 48, 6);
219
+ const rows = intIn(params.rows ?? 6, 1, 48, 6);
220
+ const gap = Math.min(0.4, Math.max(0, params.gap ?? 0.08));
221
+ const bevel = Math.min(0.5, Math.max(0.01, params.bevel ?? 0.1));
222
+ const slats = 3;
223
+ return (u, v) => {
224
+ const x = u * cols;
225
+ const y = v * rows;
226
+ const ix = Math.floor(x);
227
+ const iy = Math.floor(y);
228
+ const horizontal = (ix + iy) & 1;
229
+ const lu = fract(x);
230
+ const lv = fract(y);
231
+ const along = horizontal ? lu : lv;
232
+ const across = fract((horizontal ? lv : lu) * slats);
233
+ const slat = 1 - Math.abs(across * 2 - 1);
234
+ const face = faceProfile(lu, lv, gap, bevel);
235
+ const id = hash3u(seed, ((ix % cols) + cols) % cols, ((iy % rows) + rows) % rows, 0);
236
+ return {
237
+ v: clamp01(face * (0.45 + 0.55 * smoothstep(0.06, 0.4, slat)) * (0.85 + 0.15 * along)),
238
+ cell: id,
239
+ };
240
+ };
241
+ }
242
+
243
+ function compileHex(params, seed) {
244
+ const cols = intIn(params.columns ?? 6, 1, 48, 6);
245
+ // A staggered lattice with 2:sqrt(3) aspect produces regular hexagons
246
+ // out of plain nearest-center Voronoi. Rows derive from columns so the
247
+ // lattice stays periodic in the unit tile.
248
+ const rows = Math.max(1, Math.round(cols * 1.1547));
249
+ const gap = Math.min(0.5, Math.max(0.01, params.gap ?? 0.08));
250
+ const bevel = Math.min(0.6, Math.max(0.01, params.bevel ?? 0.14));
251
+ const variation = clamp01(params.cellVariation ?? 0.3);
252
+ return (u, v) => {
253
+ const x = u * cols;
254
+ const y = v * rows;
255
+ const yi = Math.floor(y);
256
+ let f1 = 1e9;
257
+ let f2 = 1e9;
258
+ let id = 0;
259
+ for (let dy = -1; dy <= 1; dy += 1) {
260
+ const cy = yi + dy;
261
+ const stagger = (((cy % 2) + 2) % 2) * 0.5;
262
+ const xs = x - stagger;
263
+ const xi = Math.floor(xs);
264
+ for (let dx = -1; dx <= 1; dx += 1) {
265
+ const cx = xi + dx;
266
+ const centerX = cx + 0.5 + stagger;
267
+ const centerY = cy + 0.5;
268
+ const ddx = (x - centerX) * 1;
269
+ const ddy = (y - centerY) * (cols / rows) * 1.1547;
270
+ const d = ddx * ddx + ddy * ddy;
271
+ const h = hash3u(seed, ((cx % cols) + cols) % cols, ((cy % rows) + rows) % rows, 0);
272
+ if (d < f1) {
273
+ f2 = f1;
274
+ f1 = d;
275
+ id = h;
276
+ } else if (d < f2) f2 = d;
277
+ }
278
+ }
279
+ const edge = Math.sqrt(f2) - Math.sqrt(f1);
280
+ const value = smoothstep(gap * 0.5, gap * 0.5 + bevel, edge);
281
+ return { v: withCellVariation(value, id, variation, seed), cell: id };
282
+ };
283
+ }
284
+
285
+ function compileScales(params, seed) {
286
+ const cols = intIn(params.columns ?? 8, 1, 48, 8);
287
+ const rows = intIn(params.rows ?? 12, 1, 64, 12);
288
+ const variation = clamp01(params.cellVariation ?? 0.25);
289
+ return (u, v) => {
290
+ // Staggered rows of overlapping shingles: the row above clips the one
291
+ // below, so sample this row and the next and keep the nearest arc.
292
+ const y = v * rows;
293
+ let best = 0;
294
+ let id = 0;
295
+ for (let dr = 0; dr <= 1; dr += 1) {
296
+ const row = Math.floor(y) + dr;
297
+ const stagger = (((row % 2) + 2) % 2) * 0.5;
298
+ const x = u * cols + stagger;
299
+ const col = Math.floor(x);
300
+ const cx = fract(x) - 0.5;
301
+ const cy = (y - row) * (cols / rows);
302
+ const r = Math.sqrt(cx * cx + cy * cy) * 2;
303
+ const value = clamp01(1 - r) * (1 - 0.55 * clamp01(-((y - row))));
304
+ if (value > best) {
305
+ best = value;
306
+ id = hash3u(seed, ((col % cols) + cols) % cols, ((row % (rows * 2)) + rows * 2) % (rows * 2), 0);
307
+ }
308
+ }
309
+ const shaped = clamp01(Math.pow(best, 0.7));
310
+ return { v: withCellVariation(shaped, id, variation, seed), cell: id };
311
+ };
312
+ }
313
+
314
+ function compileDots(params, seed) {
315
+ const cols = intIn(params.columns ?? 8, 1, 64, 8);
316
+ const rows = intIn(params.rows ?? 8, 1, 64, 8);
317
+ const radius = Math.min(0.48, Math.max(0.02, 0.5 - (params.gap ?? 0.18)));
318
+ const soft = Math.min(0.5, Math.max(0.01, params.bevel ?? 0.08));
319
+ const jitter = clamp01(params.cellJitter ?? 0);
320
+ const variation = clamp01(params.cellVariation ?? 0);
321
+ return (u, v) => {
322
+ const x = u * cols;
323
+ const y = v * rows;
324
+ let best = 0;
325
+ let id = 0;
326
+ const xi = Math.floor(x);
327
+ const yi = Math.floor(y);
328
+ for (let dy = -1; dy <= 1; dy += 1) {
329
+ for (let dx = -1; dx <= 1; dx += 1) {
330
+ const cx = xi + dx;
331
+ const cy = yi + dy;
332
+ const h = hash3u(seed, ((cx % cols) + cols) % cols, ((cy % rows) + rows) % rows, 0);
333
+ const ox = 0.5 + jitter * (((h & 0xffff) / 65536) - 0.5) * 0.8;
334
+ const oy = 0.5 + jitter * ((((h >>> 16) & 0xffff) / 65536) - 0.5) * 0.8;
335
+ const ddx = x - (cx + ox);
336
+ const ddy = y - (cy + oy);
337
+ const d = Math.sqrt(ddx * ddx + ddy * ddy);
338
+ const value = 1 - smoothstep(radius - soft, radius + soft, d);
339
+ if (value > best) {
340
+ best = value;
341
+ id = h;
342
+ }
343
+ }
344
+ }
345
+ return { v: withCellVariation(best, id, variation, seed), cell: best > 0.02 ? id : null };
346
+ };
347
+ }
348
+
349
+ function compileSpeckle(params, seed) {
350
+ const { px, py } = periods({ ...params, scale: Math.max(4, params.scale ?? 24) });
351
+ const density = clamp01(params.cellVariation ?? 0.5);
352
+ const size = Math.min(0.5, Math.max(0.04, params.edgeWidth ?? 0.18));
353
+ return (u, v) => {
354
+ const c = periodicCellular2(seed, u * px, v * py, px, py, 1);
355
+ const r = cellRandom(c.id, hashCombine(seed, 0x3d5));
356
+ if (r > density) return { v: 0, cell: null };
357
+ const scaleR = size * (0.4 + 1.2 * cellRandom(c.id, hashCombine(seed, 0x7a1)));
358
+ return { v: 1 - smoothstep(scaleR * 0.6, scaleR, c.f1), cell: c.id };
359
+ };
360
+ }
361
+
362
+ // ---------------------------------------------------------------------------
363
+ // Material-flavored generators
364
+ // ---------------------------------------------------------------------------
365
+
366
+ function compileMarble(params, seed) {
367
+ const { px, py } = periods(params);
368
+ const octaves = intIn(params.detail ?? 4, 1, 8, 4);
369
+ const sharp = 0.5 + (params.grain ?? 0.5) * 6;
370
+ const veins = intIn(params.rings ?? 3, 1, 24, 3);
371
+ return (u, v) => {
372
+ const turb = periodicTurbulence2(seed, u * px, v * py, px, py, octaves, 0.55);
373
+ const phase = fract(u * veins + turb * 1.35);
374
+ const wave = Math.abs(phase * 2 - 1);
375
+ return { v: clamp01(Math.pow(wave, sharp)), cell: null };
376
+ };
377
+ }
378
+
379
+ function compileWoodGrain(params, seed) {
380
+ const { px, py } = periods(params);
381
+ const rings = intIn(params.rings ?? 6, 1, 32, 6);
382
+ const grain = clamp01(params.grain ?? 0.5);
383
+ const octaves = intIn(params.detail ?? 3, 1, 8, 3);
384
+ const streakSeed = hashCombine(seed, 0xbeef);
385
+ return (u, v) => {
386
+ const wobble = periodicFbm2(seed, u * px, v * py, px, py, octaves, 0.5);
387
+ const ringPhase = fract(u * rings + wobble * 0.7);
388
+ const ring = 1 - Math.abs(ringPhase * 2 - 1);
389
+ // Fine streaks run along the grain (v axis) — heavy stretch.
390
+ const streak = periodicFbm2(streakSeed, u * px * 8, v * Math.max(1, Math.round(py / 4)), px * 8, Math.max(1, Math.round(py / 4)), 2, 0.5);
391
+ return { v: clamp01(ring * (1 - grain * 0.45) + (streak * 0.5 + 0.5) * grain * 0.55), cell: null };
392
+ };
393
+ }
394
+
395
+ function compileCracks(params, seed) {
396
+ const { px, py } = periods(params);
397
+ const width = Math.min(0.6, Math.max(0.01, params.edgeWidth ?? 0.1));
398
+ const jitter = clamp01(params.cellJitter ?? 1);
399
+ const variation = clamp01(params.cellVariation ?? 0);
400
+ return (u, v) => {
401
+ const c = periodicCellular2(seed, u * px, v * py, px, py, jitter);
402
+ const edge = c.f2 - c.f1;
403
+ const plate = smoothstep(width * 0.5, width * 1.5, edge);
404
+ return { v: withCellVariation(plate, c.id, variation, seed), cell: c.id };
405
+ };
406
+ }
407
+
408
+ function compileCaustics(params, seed) {
409
+ const { px, py } = periods(params);
410
+ const width = Math.min(0.6, Math.max(0.02, params.edgeWidth ?? 0.16));
411
+ return (u, v) => {
412
+ const c = periodicCellular2(seed, u * px, v * py, px, py, 1);
413
+ const web = 1 - smoothstep(0, width, c.f2 - c.f1);
414
+ return { v: clamp01(Math.pow(web, 1.5)), cell: null };
415
+ };
416
+ }
417
+
418
+ function compileFlat() {
419
+ return () => ({ v: 1, cell: null });
420
+ }
421
+
422
+ // ---------------------------------------------------------------------------
423
+ // Registry
424
+ // ---------------------------------------------------------------------------
425
+
426
+ const NOISE_USES = ['scale', 'detail', 'detailGain', 'stretchX', 'stretchY', 'warp', 'warpScale'];
427
+ const CELL_USES = ['scale', 'stretchX', 'stretchY', 'cellJitter', 'cellVariation', 'edgeWidth', 'warp', 'warpScale'];
428
+ const GRID_USES = ['columns', 'rows', 'gap', 'bevel', 'cellVariation', 'warp', 'warpScale'];
429
+
430
+ export const TEXTURE_GENERATORS = Object.freeze({
431
+ fbm: { label: 'Soft noise (fbm)', category: 'noise', uses: NOISE_USES, compile: makeNoise(periodicFbm2, true) },
432
+ billow: { label: 'Billow noise', category: 'noise', uses: NOISE_USES, compile: makeNoise(periodicBillow2, true) },
433
+ ridged: { label: 'Ridged noise', category: 'noise', uses: NOISE_USES, compile: makeNoise(periodicRidged2, false) },
434
+ turbulence: { label: 'Turbulence', category: 'noise', uses: NOISE_USES, compile: makeNoise(periodicTurbulence2, false) },
435
+ value: {
436
+ label: 'Blocky noise',
437
+ category: 'noise',
438
+ uses: ['scale', 'stretchX', 'stretchY', 'warp', 'warpScale'],
439
+ compile: (params, seed) => {
440
+ const { px, py } = periods(params);
441
+ return (u, v) => ({ v: clamp01(periodicValue2(seed, u * px, v * py, px, py) * 0.5 + 0.5), cell: null });
442
+ },
443
+ },
444
+ perlin: {
445
+ label: 'Smooth waves',
446
+ category: 'noise',
447
+ uses: ['scale', 'stretchX', 'stretchY', 'warp', 'warpScale'],
448
+ compile: (params, seed) => {
449
+ const { px, py } = periods(params);
450
+ return (u, v) => ({ v: clamp01(periodicPerlin2(seed, u * px, v * py, px, py) * 0.5 + 0.5), cell: null });
451
+ },
452
+ },
453
+
454
+ // pow() flattens the conical distance falloff into plateau-topped cobbles.
455
+ worley: { label: 'Cobbles (worley)', category: 'cellular', uses: CELL_USES, compile: makeCellular((c) => Math.pow(clamp01(1 - c.f1 * 1.15), 0.6)) },
456
+ worleyF2: { label: 'Cell froth', category: 'cellular', uses: CELL_USES, compile: makeCellular((c) => 1 - Math.min(1, (c.f2 - 0.35) * 1.1)) },
457
+ cells: {
458
+ label: 'Flat cells (voronoi)',
459
+ category: 'cellular',
460
+ uses: CELL_USES,
461
+ compile: makeCellular((c) => cellRandom(c.id, 0x1234)),
462
+ },
463
+ cracks: { label: 'Crack plates', category: 'cellular', uses: CELL_USES, compile: compileCracks },
464
+ caustics: { label: 'Caustic web', category: 'cellular', uses: ['scale', 'stretchX', 'stretchY', 'edgeWidth', 'warp', 'warpScale'], compile: compileCaustics },
465
+ speckle: { label: 'Speckle chips', category: 'cellular', uses: ['scale', 'cellVariation', 'edgeWidth'], compile: compileSpeckle },
466
+
467
+ bricks: { label: 'Bricks (running bond)', category: 'pattern', uses: GRID_USES, compile: (p, s) => compileBricks(p, s, 'running') },
468
+ tiles: { label: 'Tiles (stacked)', category: 'pattern', uses: GRID_USES, compile: (p, s) => compileBricks(p, s, 'stacked') },
469
+ hex: { label: 'Hex tiles', category: 'pattern', uses: ['columns', 'gap', 'bevel', 'cellVariation', 'warp', 'warpScale'], compile: compileHex },
470
+ checker: { label: 'Checker', category: 'pattern', uses: ['columns', 'rows', 'warp', 'warpScale'], compile: compileChecker },
471
+ grid: { label: 'Grid lines', category: 'pattern', uses: ['columns', 'rows', 'gap', 'bevel', 'warp', 'warpScale'], compile: compileGrid },
472
+ stripes: { label: 'Stripes', category: 'pattern', uses: ['columns', 'warp', 'warpScale'], compile: compileStripes },
473
+ chevron: { label: 'Chevron zigzag', category: 'pattern', uses: ['columns', 'rows', 'warp', 'warpScale'], compile: compileChevron },
474
+ weave: { label: 'Cloth weave', category: 'pattern', uses: ['columns', 'rows', 'gap', 'warp', 'warpScale'], compile: compileWeave },
475
+ basketWeave: { label: 'Basket weave', category: 'pattern', uses: ['columns', 'rows', 'gap', 'bevel', 'warp', 'warpScale'], compile: compileBasketWeave },
476
+ scales: { label: 'Scales / shingles', category: 'pattern', uses: ['columns', 'rows', 'cellVariation', 'warp', 'warpScale'], compile: compileScales },
477
+ dots: { label: 'Dots', category: 'pattern', uses: ['columns', 'rows', 'gap', 'bevel', 'cellJitter', 'cellVariation', 'warp', 'warpScale'], compile: compileDots },
478
+
479
+ marble: { label: 'Marble veins', category: 'material', uses: ['scale', 'detail', 'rings', 'grain', 'stretchX', 'stretchY', 'warp', 'warpScale'], compile: compileMarble },
480
+ woodGrain: { label: 'Wood grain', category: 'material', uses: ['scale', 'detail', 'rings', 'grain', 'stretchX', 'stretchY', 'warp', 'warpScale'], compile: compileWoodGrain },
481
+ flat: { label: 'Flat (accents only)', category: 'material', uses: [], compile: compileFlat },
482
+ });
483
+
484
+ export const TEXTURE_GENERATOR_IDS = Object.freeze(Object.keys(TEXTURE_GENERATORS));
485
+
486
+ /**
487
+ * Compiles a layer spec into a tileable sampler `(u, v) -> { v, cell }`.
488
+ * Applies domain warp, invert, contrast, and bias around the raw generator.
489
+ */
490
+ export function compileTextureLayer(spec, seed, salt) {
491
+ const gen = TEXTURE_GENERATORS[spec.generator] ?? TEXTURE_GENERATORS.fbm;
492
+ const layerSeed = hashCombine(seed >>> 0, salt >>> 0);
493
+ const sample = gen.compile(spec, layerSeed);
494
+ const warp = compileWarp(spec, layerSeed);
495
+ const invert = Boolean(spec.invert);
496
+ const rotate90 = Boolean(spec.rotate90);
497
+ const contrast = Math.min(1, Math.max(-1, spec.contrast ?? 0));
498
+ const bias = Math.min(0.5, Math.max(-0.5, spec.bias ?? 0));
499
+ const gainFactor = contrast >= 0 ? 1 + contrast * 3 : 1 + contrast * 0.75;
500
+ return (u0, v0) => {
501
+ // A quarter turn on the square tile keeps tiling exact.
502
+ let cu = rotate90 ? v0 : u0;
503
+ let cv = rotate90 ? 1 - u0 : v0;
504
+ const u = cu;
505
+ const v = cv;
506
+ if (warp) {
507
+ const w = warp(u, v);
508
+ cu = w.u - Math.floor(w.u);
509
+ cv = w.v - Math.floor(w.v);
510
+ }
511
+ const out = sample(cu, cv);
512
+ let value = invert ? 1 - out.v : out.v;
513
+ value = clamp01((value - 0.5) * gainFactor + 0.5 + bias);
514
+ return { v: value, cell: out.cell };
515
+ };
516
+ }