@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
@@ -90,6 +90,7 @@ export class StylizedFlower extends StylizedTree {
90
90
  trunkMaterial: createFlowerStemNodeMaterial({
91
91
  color: stemColor,
92
92
  height: trunk.height,
93
+ vegetationShader: treeOptions.vegetationShader,
93
94
  }),
94
95
  });
95
96
  this.name = 'StylizedFlower';
@@ -169,6 +170,7 @@ export class StylizedFlower extends StylizedTree {
169
170
  // bloom faces up-and-out along its twig like a real flower head instead
170
171
  // of tracking the camera.
171
172
  const headMaterial = createFlowerBloomNodeMaterial({
173
+ vegetationShader: treeOptions.vegetationShader,
172
174
  windSpeed: 1.1,
173
175
  windStrength: headSize * 0.04,
174
176
  });
@@ -205,6 +207,86 @@ export class StylizedFlower extends StylizedTree {
205
207
  };
206
208
  }
207
209
 
210
+ setWind(options = {}) {
211
+ super.setWind(options);
212
+ for (const material of [this.headsMesh?.material, this.trunkMesh?.material]) {
213
+ const uniforms = material?.uniforms;
214
+ if (!uniforms) continue;
215
+ if (options.speed !== undefined && uniforms.uWindSpeed) uniforms.uWindSpeed.value = Number(options.speed) || 0;
216
+ if (options.strength !== undefined && uniforms.uWindStrength) uniforms.uWindStrength.value = Math.max(Number(options.strength) || 0, 0);
217
+ if (options.direction !== undefined && uniforms.uWindDirection) {
218
+ const direction = Array.isArray(options.direction)
219
+ ? options.direction
220
+ : [options.direction.x, options.direction.y ?? options.direction.z];
221
+ uniforms.uWindDirection.value.set(Number(direction[0]) || 0, Number(direction[1]) || 0);
222
+ }
223
+ }
224
+ return this;
225
+ }
226
+
227
+ setSun(options = {}) {
228
+ super.setSun(options);
229
+ const uniforms = this.headsMesh?.material?.uniforms;
230
+ if (!uniforms) return this;
231
+ if (options.direction !== undefined && uniforms.uSunDirection) {
232
+ const direction = Array.isArray(options.direction)
233
+ ? options.direction
234
+ : [options.direction.x, options.direction.y, options.direction.z];
235
+ uniforms.uSunDirection.value.set(...direction).normalize();
236
+ }
237
+ if (options.color !== undefined && uniforms.uSunColor) {
238
+ const color = Array.isArray(options.color)
239
+ ? options.color
240
+ : [options.color.r, options.color.g, options.color.b];
241
+ uniforms.uSunColor.value.setRGB(...color, THREE.SRGBColorSpace);
242
+ }
243
+ if (options.sky !== undefined && uniforms.uSkyColor) {
244
+ const sky = Array.isArray(options.sky)
245
+ ? options.sky
246
+ : [options.sky.r, options.sky.g, options.sky.b];
247
+ uniforms.uSkyColor.value.setRGB(...sky, THREE.SRGBColorSpace);
248
+ }
249
+ return this;
250
+ }
251
+
252
+ setCloudShadow(options = {}) {
253
+ super.setCloudShadow(options);
254
+ for (const material of [this.headsMesh?.material, this.trunkMesh?.material]) {
255
+ const uniforms = material?.uniforms;
256
+ if (!uniforms) continue;
257
+ if (options.strength !== undefined && uniforms.uCloudShadowStrength) {
258
+ uniforms.uCloudShadowStrength.value = THREE.MathUtils.clamp(Number(options.strength) || 0, 0, 1);
259
+ }
260
+ if (options.coverage !== undefined && uniforms.uCloudShadowCoverage) {
261
+ uniforms.uCloudShadowCoverage.value = THREE.MathUtils.clamp(Number(options.coverage) || 0, 0, 1);
262
+ }
263
+ if (options.scale !== undefined && uniforms.uCloudShadowScale) {
264
+ uniforms.uCloudShadowScale.value = Math.max(Number(options.scale) || 0.0001, 0.0001);
265
+ }
266
+ if (options.velocity !== undefined && uniforms.uCloudShadowVelocity) {
267
+ const velocity = Array.isArray(options.velocity)
268
+ ? options.velocity
269
+ : [options.velocity.x, options.velocity.y];
270
+ uniforms.uCloudShadowVelocity.value.set(Number(velocity[0]) || 0, Number(velocity[1]) || 0);
271
+ }
272
+ }
273
+ return this;
274
+ }
275
+
276
+ setSurfaceWeather(options = {}) {
277
+ super.setSurfaceWeather?.(options);
278
+ for (const material of [this.headsMesh?.material, this.trunkMesh?.material]) {
279
+ const uniforms = material?.uniforms;
280
+ if (uniforms?.uWetness && options.wetness !== undefined) {
281
+ uniforms.uWetness.value = THREE.MathUtils.clamp(Number(options.wetness) || 0, 0, 1);
282
+ }
283
+ if (uniforms?.uSnowCover && options.snowCover !== undefined) {
284
+ uniforms.uSnowCover.value = THREE.MathUtils.clamp(Number(options.snowCover) || 0, 0, 1);
285
+ }
286
+ }
287
+ return this;
288
+ }
289
+
208
290
  dispose() {
209
291
  this.headsMesh.geometry.dispose();
210
292
  this.headsMesh.material.dispose();
@@ -1,6 +1,7 @@
1
1
  import * as THREE from 'three';
2
2
 
3
3
  import { createFlowerNodeMaterial } from '../shaders-tsl/flower.js';
4
+ import { applyVegetationShader } from './vegetationShaders.js';
4
5
 
5
6
  function setSrgbColor(color, rgb) {
6
7
  color.setRGB(rgb[0], rgb[1], rgb[2], THREE.SRGBColorSpace);
@@ -254,7 +255,7 @@ export const FLOWER_SETTING_FIELD_SCHEMA = Object.freeze(
254
255
  // existing callers keep working unchanged.
255
256
  export class StylizedFlowerField extends THREE.Mesh {
256
257
  constructor(options = {}) {
257
- const { placements = [] } = cleanObject(options);
258
+ const { placements = [], vegetationShader = null } = cleanObject(options);
258
259
  const settings = createFlowerSettings(options);
259
260
  const { sizeRange } = settings;
260
261
 
@@ -283,7 +284,7 @@ export class StylizedFlowerField extends THREE.Mesh {
283
284
  geometry.instanceCount = placements.length;
284
285
  geometry.boundingSphere = new THREE.Sphere(new THREE.Vector3(), 1e5);
285
286
 
286
- const material = createFlowerNodeMaterial(settings);
287
+ const material = createFlowerNodeMaterial(settings, vegetationShader);
287
288
  setSrgbColor(material.uniforms.uPetalColor.value, settings.petalColor);
288
289
  setSrgbColor(material.uniforms.uCenterColor.value, settings.centerColor);
289
290
 
@@ -324,14 +325,54 @@ export class StylizedFlowerField extends THREE.Mesh {
324
325
  }
325
326
 
326
327
  setWind({ direction, speed, strength } = {}) {
327
- this.applySettings({
328
- windDirection: direction,
329
- windSpeed: speed,
330
- windStrength: strength,
331
- });
328
+ const uniforms = this.material.uniforms;
329
+ if (direction !== undefined) {
330
+ const next = vectorArray(direction, this.settings.windDirection, 2);
331
+ uniforms.uWindDirection.value.set(next[0], next[1]);
332
+ }
333
+ if (speed !== undefined) uniforms.uWindSpeed.value = finiteNumber(speed, uniforms.uWindSpeed.value);
334
+ if (strength !== undefined) uniforms.uWindStrength.value = finiteNumber(strength, uniforms.uWindStrength.value, { min: 0 });
332
335
  return this;
333
336
  }
334
337
 
338
+ setSun({ direction, color, sky } = {}) {
339
+ const uniforms = this.material.uniforms;
340
+ if (direction !== undefined && uniforms.uSunDirection) {
341
+ const next = vectorArray(direction, [0.35, 0.72, 0.42], 3);
342
+ uniforms.uSunDirection.value.set(...next).normalize();
343
+ }
344
+ if (color !== undefined && uniforms.uSunColor) setSrgbColor(uniforms.uSunColor.value, colorArray(color, [1, 0.96, 0.84]));
345
+ if (sky !== undefined && uniforms.uSkyColor) setSrgbColor(uniforms.uSkyColor.value, colorArray(sky, [0.62, 0.78, 0.95]));
346
+ return this;
347
+ }
348
+
349
+ setCloudShadow({ strength, coverage, scale, velocity } = {}) {
350
+ const uniforms = this.material.uniforms;
351
+ if (strength !== undefined) uniforms.uCloudShadowStrength.value = finiteNumber(strength, uniforms.uCloudShadowStrength.value, { min: 0, max: 1 });
352
+ if (coverage !== undefined) uniforms.uCloudShadowCoverage.value = finiteNumber(coverage, uniforms.uCloudShadowCoverage.value, { min: 0, max: 1 });
353
+ if (scale !== undefined) uniforms.uCloudShadowScale.value = finiteNumber(scale, uniforms.uCloudShadowScale.value, { min: 0.0001 });
354
+ if (velocity !== undefined) {
355
+ const next = vectorArray(velocity, [0.02, 0.006], 2);
356
+ uniforms.uCloudShadowVelocity.value.set(next[0], next[1]);
357
+ }
358
+ return this;
359
+ }
360
+
361
+ setSurfaceWeather({ wetness, snowCover } = {}) {
362
+ const uniforms = this.material.uniforms;
363
+ if (uniforms.uWetness && wetness !== undefined) {
364
+ uniforms.uWetness.value = finiteNumber(wetness, uniforms.uWetness.value, { min: 0, max: 1 });
365
+ }
366
+ if (uniforms.uSnowCover && snowCover !== undefined) {
367
+ uniforms.uSnowCover.value = finiteNumber(snowCover, uniforms.uSnowCover.value, { min: 0, max: 1 });
368
+ }
369
+ return this;
370
+ }
371
+
372
+ setVegetationShader(profile) {
373
+ return applyVegetationShader(this, profile);
374
+ }
375
+
335
376
  update(delta) {
336
377
  this.material.uniforms.uTime.value += Math.min(Math.max(delta ?? 0.016, 0), 0.1);
337
378
  return this;
@@ -1,4 +1,5 @@
1
1
  import * as THREE from 'three';
2
+ import { applyVegetationShader } from './vegetationShaders.js';
2
3
  import { MeshBasicNodeMaterial } from 'three/webgpu';
3
4
  import {
4
5
  clamp, exp, length, max, mix, positionView, positionWorld, texture, uniform, vec4, vertexColor,
@@ -90,12 +91,14 @@ export class StylizedForest extends THREE.Group {
90
91
  castShadow = true, // near live clones cast; anchors the forest to the ground
91
92
  renderer = null, // enables texture-baked billboard impostors (strongly recommended)
92
93
  impostorTextureSize = 256, // billboard bake resolution per variant
94
+ vegetationShader = null,
93
95
  } = {}) {
94
96
  super();
95
97
  this.name = 'StylizedForest';
96
98
  this.detailDistance = detailDistance;
97
99
  this.detailCount = detailCount;
98
100
  this.updateInterval = updateInterval;
101
+ this.hasBakedImpostors = Boolean(renderer);
99
102
  this._timer = updateInterval; // force an assignment on the first update
100
103
 
101
104
  const variantCount = Math.max(1, Math.min(variants, Math.max(placements.length, 1)));
@@ -113,7 +116,7 @@ export class StylizedForest extends THREE.Group {
113
116
  if (Array.isArray(canopyColors) && canopyColors.length > 0) {
114
117
  variantSettings.tree.canopyColor = canopyColors[i % canopyColors.length];
115
118
  }
116
- const tree = new StylizedTree({ preset, seed, ...variantSettings });
119
+ const tree = new StylizedTree({ preset, seed, ...variantSettings, vegetationShader });
117
120
  this.variantTrees.push(tree);
118
121
  this._bakedVariants.push(prepareTreeForExport(tree));
119
122
  }
@@ -406,6 +409,31 @@ export class StylizedForest extends THREE.Group {
406
409
  return this;
407
410
  }
408
411
 
412
+ setWind(options) {
413
+ for (const tree of this.variantTrees) tree.setWind?.(options);
414
+ return this;
415
+ }
416
+
417
+ /** Keeps every live near-LOD tree on the current scene sun/sky inputs. */
418
+ setSun(options) {
419
+ for (const tree of this.variantTrees) tree.setSun?.(options);
420
+ return this;
421
+ }
422
+
423
+ setSurfaceWeather(options) {
424
+ for (const tree of this.variantTrees) tree.setSurfaceWeather?.(options);
425
+ return this;
426
+ }
427
+
428
+ setVegetationShader(profile) {
429
+ const report = applyVegetationShader(this.variantTrees, profile);
430
+ // Far LODs are rasterized from variant trees at construction time. Live
431
+ // materials update immediately; hosts that expose runtime style editing
432
+ // should rebuild the forest to bake matching impostors.
433
+ report.requiresImpostorRebake = this.hasBakedImpostors;
434
+ return report;
435
+ }
436
+
409
437
  /**
410
438
  * Matches the impostors' fog to the environment shader's height fog so far
411
439
  * canopies haze with the terrain they stand on. Pass the same