@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
@@ -17,13 +17,18 @@ import {
17
17
  Fn,
18
18
  instanceIndex,
19
19
  length,
20
+ floor,
21
+ max,
20
22
  mix,
21
23
  modelWorldMatrix,
22
24
  normalize,
25
+ normalWorld,
23
26
  positionLocal,
27
+ positionWorld,
24
28
  pow,
25
29
  sin,
26
30
  smoothstep,
31
+ step,
27
32
  texture,
28
33
  time,
29
34
  uniform,
@@ -35,12 +40,148 @@ import {
35
40
  vec4,
36
41
  attribute,
37
42
  } from 'three/tsl';
38
- import { MeshBasicNodeMaterial, MeshToonNodeMaterial, NodeMaterial } from 'three/webgpu';
43
+ import { MeshBasicNodeMaterial, NodeMaterial } from 'three/webgpu';
39
44
 
40
45
  import { sampleEnvironmentSunShadow } from './chunks/environment-sun-shadow.js';
46
+ import { stylizedCloudShadow } from './chunks/stylized-cloud-shadow.js';
47
+ import {
48
+ createVegetationStyleUniforms,
49
+ shadeVegetationSurface,
50
+ tagVegetationRole,
51
+ } from './chunks/vegetation-style.js';
52
+
53
+ const FLOWER_HEAD_ROLES = Object.freeze(['flowerPetal', 'flowerCenter']);
54
+
55
+ function flowerSurfaceUniforms(vegetationShader, role = FLOWER_HEAD_ROLES) {
56
+ const style = createVegetationStyleUniforms(vegetationShader, role);
57
+ return {
58
+ uCloudShadowCoverage: uniform(0.45),
59
+ uCloudShadowScale: uniform(0.012),
60
+ uCloudShadowStrength: uniform(0),
61
+ uCloudShadowVelocity: uniform(new THREE.Vector2(0.02, 0.006)),
62
+ uSkyColor: uniform(new THREE.Color().setRGB(0.62, 0.78, 0.95, THREE.SRGBColorSpace)),
63
+ uSunColor: uniform(new THREE.Color().setRGB(1, 0.96, 0.84, THREE.SRGBColorSpace)),
64
+ uSunDirection: uniform(new THREE.Vector3(0.35, 0.72, 0.42).normalize()),
65
+ ...style,
66
+ };
67
+ }
41
68
 
42
- export function createFlowerNodeMaterial(settings) {
43
- const u = {
69
+ function resolveFlowerCenterRadius(map, centerRadius) {
70
+ const value = centerRadius
71
+ ?? map?.userData?.toonlabFlower?.centerRadius
72
+ ?? map?.userData?.toonlabFlowerCenterRadius
73
+ ?? 0;
74
+ return THREE.MathUtils.clamp(Number(value) || 0, 0, 0.5);
75
+ }
76
+
77
+ function flowerCloudShadow(u, worldPosition) {
78
+ return stylizedCloudShadow(
79
+ worldPosition.xz,
80
+ time,
81
+ u.uCloudShadowStrength,
82
+ u.uCloudShadowCoverage,
83
+ u.uCloudShadowScale,
84
+ u.uCloudShadowVelocity,
85
+ );
86
+ }
87
+
88
+ function aliasLegacyFlowerUniforms(u) {
89
+ // The old one-field Flower shader remains a compatibility adapter, but all
90
+ // petal variants now share the same underlying style uniform.
91
+ u.uUnlitLift = u.uStyleFlowerUnlitPetalLift;
92
+ return u;
93
+ }
94
+
95
+ function shadeFlowerHead({
96
+ centerBaseColor,
97
+ centerMask,
98
+ normal,
99
+ petalBaseColor,
100
+ sceneShadow,
101
+ u,
102
+ worldPosition,
103
+ }) {
104
+ const cloudShadow = flowerCloudShadow(u, worldPosition);
105
+ const petal = shadeVegetationSurface({
106
+ baseColor: petalBaseColor,
107
+ bandSoftness: u.uStyleFlowerBandSoftness,
108
+ bandThreshold: u.uStyleFlowerBandThreshold,
109
+ cloudShadow,
110
+ cloudShadowResponse: 1,
111
+ normal,
112
+ sceneShadow,
113
+ sceneShadowResponse: u.uStyleFlowerSceneShadowResponse,
114
+ shadowFloor: u.uStyleThinSurfaceTransmissionShadowFloor,
115
+ skyColor: u.uSkyColor,
116
+ sunColor: u.uSunColor,
117
+ sunDirection: u.uSunDirection,
118
+ transmissionMultiplier: u.uStyleFlowerBacklitStrength.div(0.35)
119
+ .mul(u.uStyleFlowerPetalTransmissionMultiplier),
120
+ u,
121
+ worldPosition,
122
+ });
123
+ petal.color.addAssign(
124
+ petalBaseColor.mul(petal.band.oneMinus()).mul(u.uStyleFlowerUnlitPetalLift),
125
+ );
126
+
127
+ // Centers are opaque botanical structures, not thin petals. They share the
128
+ // IP lighting/weather and flower band treatment, but explicitly opt out of
129
+ // diffuse-wrap, two-sided, normal-bias, and transmission profile controls.
130
+ const centerSceneShadow = mix(1.0, sceneShadow, u.uStyleFlowerCenterShadowResponse);
131
+ const center = shadeVegetationSurface({
132
+ baseColor: centerBaseColor,
133
+ bandSoftness: u.uStyleFlowerBandSoftness,
134
+ bandThreshold: u.uStyleFlowerBandThreshold,
135
+ cloudShadow,
136
+ cloudShadowResponse: 1,
137
+ diffuseWrap: float(0.5),
138
+ normal,
139
+ normalUpBias: float(0),
140
+ sceneShadow: centerSceneShadow,
141
+ sceneShadowResponse: u.uStyleFlowerSceneShadowResponse,
142
+ shadowFloor: float(0.35),
143
+ skyColor: u.uSkyColor,
144
+ sunColor: u.uSunColor,
145
+ sunDirection: u.uSunDirection,
146
+ transmissionPower: float(3.5),
147
+ transmissionShadowFloor: float(0.35),
148
+ transmissionStrength: float(0),
149
+ twoSidedLighting: float(0),
150
+ u,
151
+ worldPosition,
152
+ });
153
+ const centerColor = mix(centerBaseColor, center.color, u.uStyleFlowerCenterLightResponse);
154
+ return mix(petal.color, centerColor, centerMask);
155
+ }
156
+
157
+ // Textured head variants carry petal and center pixels in one draw call. The
158
+ // sprite texture publishes its semantic center radius (see flowerSpecies.js),
159
+ // so the shader does not guess from color and recoloring cannot change roles.
160
+ function shadeFlowerSprite({ normal, sceneShadow, sprite, surfaceUv, u, worldPosition }) {
161
+ const radius = length(surfaceUv.sub(vec2(0.5, 0.5))).toVar();
162
+ const centerFeather = u.uCenterRadius.mul(0.12).max(0.006);
163
+ const centerMask = smoothstep(
164
+ u.uCenterRadius.add(centerFeather),
165
+ u.uCenterRadius.sub(centerFeather),
166
+ radius,
167
+ ).mul(step(0.001, u.uCenterRadius)).toVar();
168
+ const petalEdge = clamp(radius.mul(2), 0, 1).mul(centerMask.oneMinus());
169
+ const petalBaseColor = sprite.rgb.mul(
170
+ petalEdge.mul(u.uStyleFlowerCupDarkeningStrength).mul(0.8).oneMinus(),
171
+ ).toVar();
172
+ return shadeFlowerHead({
173
+ centerBaseColor: sprite.rgb,
174
+ centerMask,
175
+ normal,
176
+ petalBaseColor,
177
+ sceneShadow,
178
+ u,
179
+ worldPosition,
180
+ });
181
+ }
182
+
183
+ export function createFlowerNodeMaterial(settings, vegetationShader = null) {
184
+ const u = aliasLegacyFlowerUniforms({
44
185
  uCenterColor: uniform(new THREE.Color()),
45
186
  uPetalColor: uniform(new THREE.Color()),
46
187
  uShadowStrength: uniform(settings.shadowStrength),
@@ -48,7 +189,8 @@ export function createFlowerNodeMaterial(settings) {
48
189
  uWindDirection: uniform(new THREE.Vector2(settings.windDirection[0], settings.windDirection[1])),
49
190
  uWindSpeed: uniform(settings.windSpeed),
50
191
  uWindStrength: uniform(settings.windStrength),
51
- };
192
+ ...flowerSurfaceUniforms(vegetationShader),
193
+ });
52
194
 
53
195
  const material = new NodeMaterial();
54
196
  material.name = 'StylizedFlowers';
@@ -57,6 +199,7 @@ export function createFlowerNodeMaterial(settings) {
57
199
 
58
200
  const vPetal = varying(vec2(), 'vFlowerPetal');
59
201
  const vPhase = varying(float(), 'vFlowerPhase');
202
+ const vWorldNormal = varying(vec3(), 'vFlowerWorldNormal');
60
203
  const vWorldPosition = varying(vec3(), 'vFlowerWorldPosition');
61
204
 
62
205
  material.vertexNode = Fn(() => {
@@ -84,6 +227,11 @@ export function createFlowerNodeMaterial(settings) {
84
227
  cameraViewMatrix.element(1).y,
85
228
  cameraViewMatrix.element(2).y,
86
229
  );
230
+ vWorldNormal.assign(normalize(
231
+ vec3(0, 1, 0)
232
+ .add(cameraRight.mul(positionLocal.x).mul(0.3))
233
+ .add(cameraUp.mul(positionLocal.y).mul(0.3)),
234
+ ));
87
235
  const flowerPosition = center.add(
88
236
  cameraRight.mul(positionLocal.x).add(cameraUp.mul(positionLocal.y)).mul(iInfo.x),
89
237
  );
@@ -102,19 +250,27 @@ export function createFlowerNodeMaterial(settings) {
102
250
  Discard(radius.greaterThan(petalEdge));
103
251
 
104
252
  const centerMask = smoothstep(0.34, 0.2, radius);
105
- const color = mix(u.uPetalColor, u.uCenterColor, centerMask).toVar();
106
253
  // Petals cup slightly: darken toward their outer edge.
107
- color.mulAssign(radius.div(petalEdge).oneMinus().mul(0.12).add(0.9));
254
+ const petalBaseColor = u.uPetalColor.mul(
255
+ radius.div(petalEdge).mul(u.uStyleFlowerCupDarkeningStrength).oneMinus(),
256
+ );
108
257
 
109
- // Scene shadows: cool dark tint matched to the grass field's response.
110
258
  const sceneShadow = mix(1.0, sampleEnvironmentSunShadow(vWorldPosition), u.uShadowStrength);
111
- color.mulAssign(mix(vec3(0.44, 0.48, 0.62), vec3(1.0), sceneShadow));
259
+ const color = shadeFlowerHead({
260
+ centerBaseColor: u.uCenterColor,
261
+ centerMask,
262
+ normal: normalize(vWorldNormal),
263
+ petalBaseColor,
264
+ sceneShadow,
265
+ u,
266
+ worldPosition: vWorldPosition,
267
+ });
112
268
 
113
269
  return vec4(color, 1.0);
114
270
  })();
115
271
 
116
272
  material.uniforms = u;
117
- return material;
273
+ return tagVegetationRole(material, FLOWER_HEAD_ROLES, 'procedural');
118
274
  }
119
275
 
120
276
  // Shared instance-phased sway for the tree lab's flower patch. Heads use the
@@ -132,13 +288,18 @@ function flowerSway(u) {
132
288
  * patch's old MeshBasicMaterial billboards, plus a gentle wind bob the
133
289
  * classic material never had.
134
290
  */
135
- export function createFlowerHeadNodeMaterial({ map, alphaCutoff = 0.4, windSpeed = 1, windStrength = 0 } = {}) {
136
- const u = {
291
+ export function createFlowerHeadNodeMaterial({
292
+ map, alphaCutoff = 0.4, centerRadius = null, vegetationShader = null,
293
+ windSpeed = 1, windStrength = 0,
294
+ } = {}) {
295
+ const u = aliasLegacyFlowerUniforms({
137
296
  uAlphaCutoff: uniform(alphaCutoff),
297
+ uCenterRadius: uniform(resolveFlowerCenterRadius(map, centerRadius)),
138
298
  uMap: texture(map),
139
299
  uWindSpeed: uniform(windSpeed),
140
300
  uWindStrength: uniform(windStrength),
141
- };
301
+ ...flowerSurfaceUniforms(vegetationShader),
302
+ });
142
303
 
143
304
  const material = new MeshBasicNodeMaterial();
144
305
  material.name = 'FlowerPatchHead';
@@ -146,11 +307,23 @@ export function createFlowerHeadNodeMaterial({ map, alphaCutoff = 0.4, windSpeed
146
307
  material.transparent = true;
147
308
 
148
309
  material.positionNode = positionLocal.add(flowerSway(u));
149
- material.colorNode = u.uMap; // TextureNode samples at uv() by default
310
+ material.colorNode = Fn(() => {
311
+ const sprite = u.uMap.sample(uv());
312
+ const sceneShadow = mix(1.0, sampleEnvironmentSunShadow(positionWorld), 0.85);
313
+ const color = shadeFlowerSprite({
314
+ normal: normalize(normalWorld),
315
+ sceneShadow,
316
+ sprite,
317
+ surfaceUv: uv(),
318
+ u,
319
+ worldPosition: positionWorld,
320
+ });
321
+ return vec4(color, sprite.a);
322
+ })();
150
323
  material.alphaTestNode = u.uAlphaCutoff;
151
324
 
152
325
  material.uniforms = u;
153
- return material;
326
+ return tagVegetationRole(material, FLOWER_HEAD_ROLES, 'cutout');
154
327
  }
155
328
 
156
329
  /**
@@ -160,21 +333,26 @@ export function createFlowerHeadNodeMaterial({ map, alphaCutoff = 0.4, windSpeed
160
333
  * never renders edge-on, from any camera.
161
334
  */
162
335
  export function createFlowerHeadBillboardNodeMaterial({
163
- map, alphaCutoff = 0.4, windDirection = [1, 0.3], windSpeed = 1, windStrength = 0,
336
+ map, alphaCutoff = 0.4, centerRadius = null, vegetationShader = null,
337
+ windDirection = [1, 0.3], windSpeed = 1, windStrength = 0,
164
338
  } = {}) {
165
- const u = {
339
+ const u = aliasLegacyFlowerUniforms({
166
340
  uAlphaCutoff: uniform(alphaCutoff),
341
+ uCenterRadius: uniform(resolveFlowerCenterRadius(map, centerRadius)),
167
342
  uMap: texture(map),
168
343
  uWindDirection: uniform(new THREE.Vector2(windDirection[0], windDirection[1])),
169
344
  uWindSpeed: uniform(windSpeed),
170
345
  uWindStrength: uniform(windStrength),
171
- };
346
+ ...flowerSurfaceUniforms(vegetationShader),
347
+ });
172
348
 
173
349
  const material = new NodeMaterial();
174
350
  material.name = 'StylizedFlowerHeads';
175
351
  material.side = THREE.DoubleSide;
176
352
 
177
353
  const vUv = varying(vec2(), 'vFlowerHeadUv');
354
+ const vWorldNormal = varying(vec3(), 'vFlowerHeadNormal');
355
+ const vWorldPosition = varying(vec3(), 'vFlowerHeadWorldPosition');
178
356
 
179
357
  material.vertexNode = Fn(() => {
180
358
  const iOrigin = attribute('iOrigin', 'vec3');
@@ -207,61 +385,132 @@ export function createFlowerHeadBillboardNodeMaterial({
207
385
  cameraViewMatrix.element(1).y,
208
386
  cameraViewMatrix.element(2).y,
209
387
  );
388
+ vWorldNormal.assign(normalize(cameraUp.mul(0.65).add(vec3(0, 1, 0).mul(0.35))));
210
389
  const headPosition = center.add(
211
390
  cameraRight.mul(corner.x).add(cameraUp.mul(corner.y)).mul(iInfo.x),
212
391
  );
213
392
  const worldPosition = modelWorldMatrix.mul(vec4(headPosition, 1.0));
393
+ vWorldPosition.assign(worldPosition.xyz);
214
394
  return cameraProjectionMatrix.mul(cameraViewMatrix).mul(worldPosition);
215
395
  })();
216
396
 
217
397
  material.fragmentNode = Fn(() => {
218
398
  const sprite = u.uMap.sample(vUv).toVar();
219
399
  Discard(sprite.a.lessThan(u.uAlphaCutoff));
220
- return vec4(sprite.rgb, 1.0);
400
+ const sceneShadow = mix(1.0, sampleEnvironmentSunShadow(vWorldPosition), 0.85);
401
+ const color = shadeFlowerSprite({
402
+ normal: normalize(vWorldNormal),
403
+ sceneShadow,
404
+ sprite,
405
+ surfaceUv: vUv,
406
+ u,
407
+ worldPosition: vWorldPosition,
408
+ });
409
+ return vec4(color, 1.0);
221
410
  })();
222
411
 
223
412
  material.uniforms = u;
224
- return material;
413
+ return tagVegetationRole(material, FLOWER_HEAD_ROLES, 'billboard');
225
414
  }
226
415
 
227
416
  /**
228
417
  * Toon material for 3D bloom-head meshes (vertex-colored petals + center
229
418
  * from createFlowerHeadGeometry), with the shared instance-phased wind bob.
230
419
  */
231
- export function createFlowerBloomNodeMaterial({ windSpeed = 1, windStrength = 0 } = {}) {
232
- const u = {
420
+ export function createFlowerBloomNodeMaterial({
421
+ unlitLift = 0.35, vegetationShader = null, windSpeed = 1, windStrength = 0,
422
+ } = {}) {
423
+ const u = aliasLegacyFlowerUniforms({
233
424
  uWindSpeed: uniform(windSpeed),
234
425
  uWindStrength: uniform(windStrength),
235
- };
426
+ ...flowerSurfaceUniforms(vegetationShader),
427
+ });
428
+ if (!vegetationShader) u.uStyleFlowerUnlitPetalLift.value = unlitLift;
236
429
 
237
- const material = new MeshToonNodeMaterial({ side: THREE.DoubleSide, vertexColors: true });
430
+ const material = new NodeMaterial();
238
431
  material.name = 'StylizedFlowerBloom';
432
+ material.side = THREE.DoubleSide;
239
433
  material.positionNode = positionLocal.add(flowerSway(u));
240
- // Petal-tinted floor for unlit faces: cup interiors and shaded petals
241
- // read as darker petal color instead of toon-band black.
242
- material.emissiveNode = vertexColor().rgb.mul(0.35);
434
+ material.fragmentNode = Fn(() => {
435
+ const centerMask = attribute('flowerRole', 'float');
436
+ const petalEdge = abs(uv().x.sub(0.5)).mul(2).mul(centerMask.oneMinus());
437
+ const rawColor = vertexColor().rgb;
438
+ const petalBaseColor = rawColor.mul(
439
+ petalEdge.mul(u.uStyleFlowerCupDarkeningStrength).mul(0.8).oneMinus(),
440
+ );
441
+ const sceneShadow = sampleEnvironmentSunShadow(positionWorld);
442
+ const color = shadeFlowerHead({
443
+ centerBaseColor: rawColor,
444
+ centerMask,
445
+ normal: normalize(normalWorld),
446
+ petalBaseColor,
447
+ sceneShadow,
448
+ u,
449
+ worldPosition: positionWorld,
450
+ });
451
+ return vec4(color, 1.0);
452
+ })();
243
453
 
244
454
  material.uniforms = u;
245
- return material;
455
+ return tagVegetationRole(material, FLOWER_HEAD_ROLES, 'mesh');
246
456
  }
247
457
 
248
458
  /**
249
459
  * Toon stem material for ground flowers. Bends with the same sway as the
250
460
  * head, scaled by height fraction (root stays planted, tip follows the head).
251
461
  */
252
- export function createFlowerStemNodeMaterial({ color = 0x4d8a3f, height = 1, windSpeed = 1, windStrength = 0 } = {}) {
462
+ export function createFlowerStemNodeMaterial({
463
+ color = 0x4d8a3f, height = 1, vegetationShader = null, windSpeed = 1, windStrength = 0,
464
+ } = {}) {
253
465
  const u = {
466
+ uBaseColor: uniform(new THREE.Color(color)),
254
467
  uHeight: uniform(Math.max(height, 1e-3)),
255
468
  uWindSpeed: uniform(windSpeed),
256
469
  uWindStrength: uniform(windStrength),
470
+ ...flowerSurfaceUniforms(vegetationShader, 'herbaceousStem'),
257
471
  };
258
472
 
259
- const material = new MeshToonNodeMaterial({ color });
473
+ const material = new NodeMaterial();
260
474
  material.name = 'FlowerPatchStem';
261
475
 
262
476
  const bend = clamp(positionLocal.y.div(u.uHeight), 0.0, 1.0);
263
477
  material.positionNode = positionLocal.add(flowerSway(u).mul(bend));
478
+ material.fragmentNode = Fn(() => {
479
+ const normal = normalize(normalWorld);
480
+ const sunDirection = normalize(u.uSunDirection);
481
+ const wrap = dot(normal, sunDirection).mul(0.5).add(0.5);
482
+ const steps = max(floor(u.uStyleStemBandCount), 2.0);
483
+ const intervals = max(steps.sub(1.0), 1.0);
484
+ const stepped = floor(clamp(wrap, 0, 1).mul(intervals).add(1e-4)).div(intervals);
485
+ const band = mix(stepped, wrap, u.uStyleStemBandSoftness);
486
+ const shaded = shadeVegetationSurface({
487
+ bandOverride: band,
488
+ baseColor: u.uBaseColor,
489
+ bandSoftness: u.uStyleStemBandSoftness,
490
+ bandThreshold: 0.5,
491
+ cloudShadow: flowerCloudShadow(u, positionWorld),
492
+ cloudShadowResponse: 1,
493
+ diffuseWrap: float(0.5),
494
+ normal,
495
+ normalUpBias: float(0),
496
+ sceneShadow: sampleEnvironmentSunShadow(positionWorld),
497
+ sceneShadowResponse: 1,
498
+ shadowFloor: u.uStyleStemShadowFloor,
499
+ skyColor: u.uSkyColor,
500
+ skyFillStrength: u.uStyleLightingSkyFillStrength.add(u.uStyleStemSkyFillStrength),
501
+ rimStrength: u.uStyleLightingRimStrength.add(u.uStyleStemRimStrength),
502
+ sunColor: u.uSunColor,
503
+ sunDirection,
504
+ transmissionPower: float(3.5),
505
+ transmissionShadowFloor: float(0.35),
506
+ transmissionStrength: u.uStyleStemTransmissionStrength,
507
+ twoSidedLighting: float(0),
508
+ u,
509
+ worldPosition: positionWorld,
510
+ });
511
+ return vec4(shaded.color, 1.0);
512
+ })();
264
513
 
265
514
  material.uniforms = u;
266
- return material;
515
+ return tagVegetationRole(material, 'herbaceousStem', 'mesh');
267
516
  }
@@ -41,10 +41,15 @@ import { NodeMaterial } from 'three/webgpu';
41
41
 
42
42
  import { sampleEnvironmentSunShadow } from './chunks/environment-sun-shadow.js';
43
43
  import { stylizedCloudShadow } from './chunks/stylized-cloud-shadow.js';
44
+ import {
45
+ createVegetationStyleUniforms,
46
+ shadeVegetationSurface,
47
+ tagVegetationRole,
48
+ } from './chunks/vegetation-style.js';
44
49
 
45
- export function createGrassNodeMaterial(settings) {
50
+ export function createGrassNodeMaterial(settings, vegetationShader = null) {
51
+ const styleUniforms = createVegetationStyleUniforms(vegetationShader, 'grassBlade');
46
52
  const u = {
47
- uBacklitStrength: uniform(settings.backlitStrength),
48
53
  uBaseColor: uniform(new THREE.Color()),
49
54
  uCloudShadowCoverage: uniform(settings.cloudShadowCoverage),
50
55
  uCloudShadowScale: uniform(settings.cloudShadowScale),
@@ -53,6 +58,7 @@ export function createGrassNodeMaterial(settings) {
53
58
  uFadeEnd: uniform(1e6 + 1),
54
59
  uFadeStart: uniform(1e6),
55
60
  uGustFrequency: uniform(settings.gustFrequency),
61
+ uGustResponse: uniform(settings.gustResponse),
56
62
  uGustSpeed: uniform(settings.gustSpeed),
57
63
  uPushPosition: uniform(new THREE.Vector3(0, -1e5, 0)),
58
64
  uPushRadius: uniform(settings.pushRadius),
@@ -64,9 +70,14 @@ export function createGrassNodeMaterial(settings) {
64
70
  uTime: uniform(0),
65
71
  uTipColor: uniform(new THREE.Color()),
66
72
  uWindDirection: uniform(new THREE.Vector2(settings.windDirection[0], settings.windDirection[1])),
73
+ uWindResponse: uniform(settings.windResponse),
67
74
  uWindSpeed: uniform(settings.windSpeed),
68
75
  uWindStrength: uniform(settings.windStrength),
76
+ ...styleUniforms,
69
77
  };
78
+ if (!vegetationShader) u.uStyleGrassBacklitStrength.value = settings.backlitStrength;
79
+ // Compatibility alias: legacy runtime setters now target the canonical style node.
80
+ u.uBacklitStrength = u.uStyleGrassBacklitStrength;
70
81
 
71
82
  const material = new NodeMaterial();
72
83
  material.name = 'StylizedGrass';
@@ -92,7 +103,7 @@ export function createGrassNodeMaterial(settings) {
92
103
  bladePosition.xz.addAssign(facing.mul(positionLocal.x.mul(iInfo.z)));
93
104
  bladePosition.y.addAssign(heightFraction.mul(iInfo.x));
94
105
 
95
- const bendCurve = heightFraction.mul(heightFraction).toVar();
106
+ const bendCurve = pow(heightFraction, u.uStyleGrassBendExponent).toVar();
96
107
 
97
108
  // Static per-blade lean (see grass.vert.glsl).
98
109
  const bowDirection = vec2(facing.y.negate(), facing.x);
@@ -116,8 +127,11 @@ export function createGrassNodeMaterial(settings) {
116
127
  const phase = u.uTime.mul(u.uWindSpeed).add(iInfo.y.mul(6.2831))
117
128
  .add(dot(iOrigin.xz, vec2(0.35, 0.28)));
118
129
  const flutter = sin(phase).mul(0.5).add(0.5).add(sin(phase.mul(2.33).add(1.7)).mul(0.3));
119
- const wind = windDirection.mul(flutter.mul(0.6).add(vGust.mul(1.1))).toVar();
120
- bladePosition.xz.addAssign(wind.mul(u.uWindStrength).mul(bendCurve).mul(iInfo.x));
130
+ const wind = windDirection.mul(
131
+ flutter.mul(0.6).add(vGust.mul(1.1).mul(u.uGustResponse)),
132
+ ).toVar();
133
+ const windAmplitude = u.uWindStrength.mul(u.uWindResponse).toVar();
134
+ bladePosition.xz.addAssign(wind.mul(windAmplitude).mul(bendCurve).mul(iInfo.x));
121
135
 
122
136
  // Character push.
123
137
  const fromPush = bladePosition.xz.sub(u.uPushPosition.xz).toVar();
@@ -125,12 +139,15 @@ export function createGrassNodeMaterial(settings) {
125
139
  const push = smoothstep(0.0, u.uPushRadius, pushDistance).oneMinus()
126
140
  .mul(step(abs(iOrigin.y.sub(u.uPushPosition.y)), 1.8));
127
141
  bladePosition.xz.addAssign(
128
- normalize(fromPush.add(vec2(1e-4, 0.0))).mul(push).mul(0.42).mul(bendCurve).mul(iInfo.x),
142
+ normalize(fromPush.add(vec2(1e-4, 0.0))).mul(push).mul(0.42)
143
+ .mul(bendCurve).mul(iInfo.x).mul(u.uStyleGrassInteractionResponse),
144
+ );
145
+ bladePosition.y.subAssign(
146
+ push.mul(0.14).mul(bendCurve).mul(iInfo.x).mul(u.uStyleGrassInteractionResponse),
129
147
  );
130
- bladePosition.y.subAssign(push.mul(0.14).mul(bendCurve).mul(iInfo.x));
131
148
 
132
149
  // Shared field normal, tilted by the current bend.
133
- const tilt = wind.mul(u.uWindStrength).add(leanDirection.mul(leanAmount).mul(0.35)).mul(heightFraction);
150
+ const tilt = wind.mul(windAmplitude).add(leanDirection.mul(leanAmount).mul(0.35)).mul(heightFraction);
134
151
  vNormal.assign(normalize(vec3(tilt.x.mul(1.4), 1.0, tilt.y.mul(1.4))));
135
152
 
136
153
  // Distance fade to a degenerate point.
@@ -149,12 +166,20 @@ export function createGrassNodeMaterial(settings) {
149
166
  const halfWidth = taper.mul(0.5);
150
167
  Discard(abs(vUv.x.sub(0.5)).greaterThan(halfWidth));
151
168
 
152
- const tipMix = smoothstep(0.1, 0.95, vUv.y.mul(vJitter.mul(0.3).add(0.85))).toVar();
169
+ const tipMix = smoothstep(
170
+ u.uStyleGrassTipGradientStart,
171
+ u.uStyleGrassTipGradientEnd,
172
+ vUv.y.mul(vJitter.mul(0.3).add(0.85)),
173
+ ).toVar();
153
174
  const color = mix(u.uBaseColor, u.uTipColor, tipMix).toVar();
154
- color.mulAssign(vJitter.mul(0.2).add(0.9));
175
+ color.mulAssign(vJitter.sub(0.5).mul(u.uStyleGrassColorVariationStrength).add(1.0));
155
176
 
156
177
  // Dense-field AO toward the roots.
157
- color.mulAssign(mix(0.64, 1.0, smoothstep(0.0, 0.62, vUv.y)));
178
+ color.mulAssign(mix(
179
+ u.uStyleGrassRootOcclusionStrength.oneMinus(),
180
+ 1.0,
181
+ smoothstep(0.0, u.uStyleGrassRootOcclusionHeight, vUv.y),
182
+ ));
158
183
 
159
184
  const normal = normalize(vNormal);
160
185
  const sunDirection = normalize(u.uSunDirection);
@@ -163,31 +188,33 @@ export function createGrassNodeMaterial(settings) {
163
188
  u.uCloudShadowStrength, u.uCloudShadowCoverage, u.uCloudShadowScale, u.uCloudShadowVelocity,
164
189
  );
165
190
  const sceneShadow = mix(1.0, sampleEnvironmentSunShadow(vWorldPosition), u.uShadowStrength);
166
- const sunVisibility = cloudShadow.mul(sceneShadow).toVar();
167
- const wrap = dot(normal, sunDirection).mul(0.5).add(0.5);
168
- const litBand = smoothstep(0.44, 0.54, wrap).mul(sunVisibility).toVar();
169
- color.mulAssign(mix(0.86, 1.06, litBand));
170
- color.mulAssign(mix(vec3(1.0), u.uSunColor, litBand.mul(0.25)));
171
-
172
- // Occlusion darkening shared with the terrain response.
173
- color.mulAssign(mix(u.uShadowTint, vec3(1.0), sunVisibility));
174
-
175
- // Sky fill + gust sheen.
176
- color.addAssign(u.uSkyColor.mul(litBand.oneMinus()).mul(0.07));
177
- const sheen = smoothstep(0.78, 1.0, vGust).mul(tipMix);
178
- color.addAssign(u.uSunColor.mul(sheen).mul(0.22).mul(litBand));
179
-
180
- // Backlit translucency.
181
- const viewDirection = normalize(cameraPosition.sub(vWorldPosition));
182
- const backlit = pow(clamp(dot(viewDirection, sunDirection.negate()), 0.0, 1.0), 3.0);
183
- color.addAssign(
184
- u.uSunColor.mul(backlit).mul(tipMix).mul(u.uBacklitStrength)
185
- .mul(sunVisibility.mul(0.65).add(0.35)),
191
+ const shaded = shadeVegetationSurface({
192
+ baseColor: color,
193
+ bandSoftness: u.uStyleGrassBandSoftness,
194
+ bandThreshold: u.uStyleGrassBandThreshold,
195
+ cloudShadow,
196
+ cloudShadowResponse: u.uStyleGrassCloudShadowResponse,
197
+ materialShadowColor: u.uShadowTint,
198
+ normal,
199
+ sceneShadow,
200
+ sceneShadowResponse: u.uStyleGrassSceneShadowResponse,
201
+ shadowFloor: u.uStyleGrassShadowFloor,
202
+ skyColor: u.uSkyColor,
203
+ sunColor: u.uSunColor,
204
+ sunDirection,
205
+ transmissionMultiplier: u.uStyleGrassBacklitStrength.div(0.35).mul(tipMix),
206
+ u,
207
+ worldPosition: vWorldPosition,
208
+ });
209
+
210
+ const sheen = smoothstep(u.uStyleGrassGustSheenThreshold, 1.0, vGust).mul(tipMix);
211
+ shaded.color.addAssign(
212
+ u.uSunColor.mul(sheen).mul(u.uStyleGrassGustSheenStrength).mul(shaded.band),
186
213
  );
187
214
 
188
- return vec4(color, 1.0);
215
+ return vec4(shaded.color, 1.0);
189
216
  })();
190
217
 
191
218
  material.uniforms = u;
192
- return material;
219
+ return tagVegetationRole(material, 'grassBlade', 'procedural');
193
220
  }