@call-me-sensei/toonlab 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +127 -4
- package/ATTRIBUTION.md +41 -3
- package/README.md +234 -34
- package/docs/characters.md +144 -0
- package/docs/debug-panel.md +126 -0
- package/docs/docs.css +589 -0
- package/docs/environment.md +186 -0
- package/docs/getting-started.md +180 -0
- package/docs/index.html +14 -0
- package/docs/lab-architecture.md +100 -0
- package/docs/lighting.md +825 -0
- package/docs/main.jsx +739 -0
- package/docs/mcp.md +97 -0
- package/docs/post-processing.md +98 -0
- package/docs/settings-reference.md +1878 -0
- package/docs/shader-constants.md +77 -0
- package/docs/sky.md +182 -0
- package/docs/style-labs.md +309 -0
- package/docs/texture-lab.md +135 -0
- package/docs/toon-shading.md +183 -0
- package/docs/tsl-conventions.md +167 -0
- package/docs/vegetation-sky.md +275 -0
- package/docs/water.md +430 -0
- package/docs/weather.md +200 -0
- package/docs/world-scale.md +111 -0
- package/mcp/server.mjs +610 -0
- package/mcp/style-lab-tools.mjs +371 -0
- package/mcp/vite-plugin.mjs +174 -0
- package/mcp/workspace.mjs +397 -0
- package/package.json +64 -5
- package/src/ambientfx/INTEGRATION.md +164 -0
- package/src/ambientfx/ambientFxPresets.js +83 -0
- package/src/ambientfx/ambientFxSettings.js +368 -0
- package/src/ambientfx/emitters.js +169 -0
- package/src/ambientfx/index.js +5 -0
- package/src/ambientfx/particleBackbone.js +493 -0
- package/src/ambientfx/stylizedAmbientFx.js +450 -0
- package/src/assetlib/ambientcg.js +117 -0
- package/src/assetlib/assetRef.js +91 -0
- package/src/assetlib/importedEntry.js +40 -0
- package/src/assetlib/index.js +23 -0
- package/src/assetlib/kaykit.js +192 -0
- package/src/assetlib/kaykitStaticIndex.js +700 -0
- package/src/assetlib/loadImported.js +143 -0
- package/src/assetlib/opensource3d.js +113 -0
- package/src/assetlib/polyhaven.js +182 -0
- package/src/assetlib/polypizza.js +115 -0
- package/src/assetlib/smithsonian.js +214 -0
- package/src/assetlib/sources.js +279 -0
- package/src/assetlib/zip.js +58 -0
- package/src/biome/biomeGenerator.js +385 -0
- package/src/biome/biomeRuntime.js +299 -0
- package/src/biome/index.js +2 -0
- package/src/buildinggen/buildingAsset.js +44 -0
- package/src/buildinggen/buildingGrammar.js +311 -0
- package/src/buildinggen/buildingMesh.js +451 -0
- package/src/buildinggen/buildingPresets.js +46 -0
- package/src/buildinggen/buildingRecipe.js +100 -0
- package/src/buildinggen/buildingSettings.js +238 -0
- package/src/buildinggen/index.js +6 -0
- package/src/camera/cameraDirector.js +157 -0
- package/src/camera/cameraGenerator.js +367 -0
- package/src/camera/cameraRig.js +570 -0
- package/src/camera/cameraSettings.js +236 -0
- package/src/camera/index.js +7 -0
- package/src/catalog/builtinEntries.js +245 -0
- package/src/catalog/catalog.js +210 -0
- package/src/catalog/index.js +3 -0
- package/src/catalog/manifest.js +84 -0
- package/src/core/generation.js +529 -0
- package/src/environment/environmentRigs.js +21 -1
- package/src/environment/environmentSettings.js +4 -0
- package/src/environment/environmentSunShadowPass.js +8 -0
- package/src/fauna/INTEGRATION.md +174 -0
- package/src/fauna/boids.js +861 -0
- package/src/fauna/faunaBodies.js +492 -0
- package/src/fauna/faunaPresets.js +52 -0
- package/src/fauna/faunaSettings.js +525 -0
- package/src/fauna/index.js +5 -0
- package/src/fauna/stylizedFauna.js +395 -0
- package/src/game-feel/gameFeelGenerator.js +402 -0
- package/src/game-feel/gameFeelRuntime.js +549 -0
- package/src/game-feel/index.js +2 -0
- package/src/index.js +17 -6
- package/src/lighting/colorIntensity.js +177 -0
- package/src/lighting/index.js +161 -0
- package/src/lighting/lightDescriptors.js +249 -0
- package/src/lighting/lightingCapabilities.js +79 -0
- package/src/lighting/lightingDocuments.js +247 -0
- package/src/lighting/lightingFixtures.js +446 -0
- package/src/lighting/lightingGenerator.js +449 -0
- package/src/lighting/lightingPresets.js +319 -0
- package/src/lighting/lightingRuntime.js +723 -0
- package/src/lighting/lightingStyle.js +386 -0
- package/src/lighting/lightingSystem.js +774 -0
- package/src/lighting/unrealExport.js +186 -0
- package/src/lighting/utils.js +87 -0
- package/src/motion/index.js +5 -0
- package/src/motion/motionClip.js +441 -0
- package/src/motion/motionController.js +628 -0
- package/src/motion/motionDocuments.js +225 -0
- package/src/motion/motionGraph.js +307 -0
- package/src/motion/motionSettings.js +222 -0
- package/src/pathgen/index.js +7 -0
- package/src/pathgen/pathBridge.js +232 -0
- package/src/pathgen/pathPresets.js +35 -0
- package/src/pathgen/pathRibbon.js +410 -0
- package/src/pathgen/pathRouter.js +380 -0
- package/src/pathgen/pathSettings.js +335 -0
- package/src/pathgen/pathTextures.js +123 -0
- package/src/pathgen/stylizedPaths.js +453 -0
- package/src/post/index.js +1 -0
- package/src/post/postGenerator.js +177 -0
- package/src/post/postProcessing.js +41 -0
- package/src/propgen/generatorsWave1.js +379 -0
- package/src/propgen/generatorsWave2.js +462 -0
- package/src/propgen/index.js +5 -0
- package/src/propgen/propAsset.js +323 -0
- package/src/propgen/propParts.js +170 -0
- package/src/propgen/propPlacement.js +459 -0
- package/src/propgen/propPresets.js +82 -0
- package/src/propgen/propSettings.js +395 -0
- package/src/shaders-tsl/chunks/projected-water-caustics.js +242 -0
- package/src/shaders-tsl/chunks/vegetation-style.js +360 -0
- package/src/shaders-tsl/chunks/water-shore-state.js +31 -0
- package/src/shaders-tsl/chunks/water-waves.js +90 -11
- package/src/shaders-tsl/environment.js +15 -1
- package/src/shaders-tsl/flower.js +279 -30
- package/src/shaders-tsl/grass.js +60 -33
- package/src/shaders-tsl/sky.js +125 -31
- package/src/shaders-tsl/tree-leaf.js +61 -25
- package/src/shaders-tsl/water-breaker.js +7 -4
- package/src/shaders-tsl/water-shore-state-simulation.js +523 -0
- package/src/shaders-tsl/water.js +439 -49
- package/src/shaders-tsl/woody-surface.js +154 -0
- package/src/sky/sceneOverrideLayers.js +10 -0
- package/src/sky/skyQuality.js +26 -0
- package/src/sky/stylizedSky.js +753 -45
- package/src/soundscape/index.js +4 -0
- package/src/soundscape/soundscapeGenerator.js +179 -0
- package/src/soundscape/soundscapeRuntime.js +806 -0
- package/src/soundscape/soundscapeSettings.js +292 -0
- package/src/styles/index.js +13 -0
- package/src/styles/styleBundle.js +325 -0
- package/src/stylizedTerrain.js +32 -2
- package/src/stylizedWorld.js +423 -20
- package/src/texgen/evaluateTexture.js +675 -0
- package/src/texgen/index.js +60 -0
- package/src/texgen/noise2.js +210 -0
- package/src/texgen/textureAi.js +436 -0
- package/src/texgen/textureGenerators.js +516 -0
- package/src/texgen/texturePresets.js +490 -0
- package/src/texgen/textureSettings.js +342 -0
- package/src/texgen/textureThree.js +59 -0
- package/src/vegetation/flowerSpecies.js +15 -3
- package/src/vegetation/grassPalettes.js +153 -0
- package/src/vegetation/index.js +6 -0
- package/src/vegetation/stylizedBush.js +2 -0
- package/src/vegetation/stylizedFlower.js +82 -0
- package/src/vegetation/stylizedFlowers.js +48 -7
- package/src/vegetation/stylizedForest.js +29 -1
- package/src/vegetation/stylizedGrass.js +291 -56
- package/src/vegetation/stylizedTree.js +38 -2
- package/src/vegetation/stylizedTreeFoliage.js +2 -1
- package/src/vegetation/vegetationShaders.js +1110 -0
- package/src/vfxgen/INTEGRATION.md +145 -0
- package/src/vfxgen/core/burstBackbone.js +380 -0
- package/src/vfxgen/core/projectileCore.js +92 -0
- package/src/vfxgen/core/spriteShapes.js +98 -0
- package/src/vfxgen/core/trailRibbon.js +272 -0
- package/src/vfxgen/core/vfxRandom.js +29 -0
- package/src/vfxgen/effects/emitHelpers.js +37 -0
- package/src/vfxgen/effects/magicEffects.js +162 -0
- package/src/vfxgen/effects/movementEffects.js +87 -0
- package/src/vfxgen/effects/weaponEffects.js +118 -0
- package/src/vfxgen/index.js +18 -0
- package/src/vfxgen/moves/moveController.js +146 -0
- package/src/vfxgen/moves/moveLibrary.js +335 -0
- package/src/vfxgen/vfxPresets.js +98 -0
- package/src/vfxgen/vfxSettings.js +384 -0
- package/src/vfxgen/vfxSystem.js +449 -0
- package/src/vfxgen/weapons/stylizedWeapons.js +137 -0
- package/src/villagegen/index.js +4 -0
- package/src/villagegen/stylizedVillage.js +490 -0
- package/src/villagegen/villageArchetypes.js +160 -0
- package/src/villagegen/villageNames.js +40 -0
- package/src/villagegen/villageSites.js +105 -0
- package/src/water/sceneOverrideLayers.js +23 -0
- package/src/water/water.js +5 -0
- package/src/water/waterBreakerSystem.js +15 -1
- package/src/water/waterCurrentField.js +447 -0
- package/src/water/waterMaterial.js +12 -0
- package/src/water/waterNearshorePhase.js +320 -0
- package/src/water/waterScenePasses.js +83 -30
- package/src/water/waterSettings.js +325 -13
- package/src/water/waterShoreMaterial.js +322 -0
- package/src/water/waterShoreStateField.js +605 -0
- package/src/water/waterSurface.js +797 -28
- package/src/weather/index.js +6 -0
- package/src/weather/weatherPrecipitation.js +221 -0
- package/src/weather/weatherPresets.js +258 -0
- package/src/weather/weatherSettings.js +269 -0
- package/src/weather/weatherSystem.js +871 -0
- package/src/worldMinimap.js +62 -0
- package/src/worldPresets.js +4 -1
|
@@ -1,6 +1,20 @@
|
|
|
1
1
|
import * as THREE from 'three';
|
|
2
2
|
|
|
3
|
+
import {
|
|
4
|
+
createSettingsPresetDocument,
|
|
5
|
+
parsePresetDocument,
|
|
6
|
+
serializePresetDocument,
|
|
7
|
+
validateSettingsPresetDocument,
|
|
8
|
+
} from '../core/presetDocuments.js';
|
|
3
9
|
import { createGrassNodeMaterial } from '../shaders-tsl/grass.js';
|
|
10
|
+
import { applyVegetationShader } from './vegetationShaders.js';
|
|
11
|
+
|
|
12
|
+
export {
|
|
13
|
+
GRASS_COLOR_PALETTES,
|
|
14
|
+
applyGrassColorPalette,
|
|
15
|
+
matchGrassColorPalette,
|
|
16
|
+
resolveGrassColorPalette,
|
|
17
|
+
} from './grassPalettes.js';
|
|
4
18
|
|
|
5
19
|
const pushScratch = new THREE.Vector3();
|
|
6
20
|
|
|
@@ -61,6 +75,7 @@ export const DEFAULT_GRASS_SETTINGS = Object.freeze({
|
|
|
61
75
|
cloudShadowStrength: 0,
|
|
62
76
|
cloudShadowVelocity: Object.freeze([0.02, 0.006]),
|
|
63
77
|
gustFrequency: 0.35,
|
|
78
|
+
gustResponse: 1,
|
|
64
79
|
gustSpeed: 1.6,
|
|
65
80
|
pushRadius: 0.9,
|
|
66
81
|
shadowStrength: 0.9,
|
|
@@ -70,10 +85,17 @@ export const DEFAULT_GRASS_SETTINGS = Object.freeze({
|
|
|
70
85
|
sunDirection: Object.freeze([0.35, 0.72, 0.42]),
|
|
71
86
|
tipColor: Object.freeze([0.74, 0.9, 0.42]),
|
|
72
87
|
windDirection: Object.freeze([1, 0.3]),
|
|
88
|
+
windResponse: 1,
|
|
73
89
|
windSpeed: 1.0,
|
|
74
90
|
windStrength: 0.16,
|
|
75
91
|
});
|
|
76
92
|
|
|
93
|
+
/** Document `type` discriminator for portable grass presets. */
|
|
94
|
+
export const GRASS_PRESET_DOCUMENT_TYPE = 'toonlab/grass-preset';
|
|
95
|
+
|
|
96
|
+
/** Current portable grass preset schema version. */
|
|
97
|
+
export const GRASS_PRESET_SCHEMA_VERSION = 2;
|
|
98
|
+
|
|
77
99
|
// Named grass presets: 'default' is the baseline; 'call_me_sensei' is the
|
|
78
100
|
// studio-managed signature look, curated and updated over releases.
|
|
79
101
|
// Community presets register alongside them via registerGrassPreset().
|
|
@@ -89,6 +111,7 @@ const grassPresetRegistry = new Map([
|
|
|
89
111
|
settings: Object.freeze({}),
|
|
90
112
|
})],
|
|
91
113
|
]);
|
|
114
|
+
const BUILT_IN_GRASS_PRESET_IDS = new Set(['default', 'call_me_sensei']);
|
|
92
115
|
|
|
93
116
|
/**
|
|
94
117
|
* Registers a named grass preset so it resolves in `createGrassSettings({
|
|
@@ -96,19 +119,17 @@ const grassPresetRegistry = new Map([
|
|
|
96
119
|
* settings? }` or flat settings.
|
|
97
120
|
*/
|
|
98
121
|
export function registerGrassPreset(name, preset = {}, { overwrite = false } = {}) {
|
|
99
|
-
const
|
|
100
|
-
if (!
|
|
101
|
-
|
|
102
|
-
throw new Error(`Grass preset "${id}" already exists.`);
|
|
122
|
+
const document = createGrassPresetDocument(name, preset);
|
|
123
|
+
if (!overwrite && grassPresetRegistry.has(document.id)) {
|
|
124
|
+
throw new Error(`Grass preset "${document.id}" already exists.`);
|
|
103
125
|
}
|
|
104
|
-
const { label, description, settings, ...flat } = cleanObject(preset);
|
|
105
126
|
const entry = Object.freeze({
|
|
106
|
-
description:
|
|
107
|
-
label:
|
|
108
|
-
settings: Object.freeze({ ...
|
|
127
|
+
description: document.description,
|
|
128
|
+
label: document.label,
|
|
129
|
+
settings: Object.freeze({ ...document.settings }),
|
|
109
130
|
});
|
|
110
|
-
grassPresetRegistry.set(id, entry);
|
|
111
|
-
return { description: entry.description, id, label: entry.label };
|
|
131
|
+
grassPresetRegistry.set(document.id, entry);
|
|
132
|
+
return { description: entry.description, id: document.id, label: entry.label };
|
|
112
133
|
}
|
|
113
134
|
|
|
114
135
|
/** Lists registered grass presets as `{ id, label, description }` (for HUDs). */
|
|
@@ -120,6 +141,13 @@ export function getGrassPresetOptions() {
|
|
|
120
141
|
}));
|
|
121
142
|
}
|
|
122
143
|
|
|
144
|
+
/** Removes a registered community/local preset. Built-in preset ids are protected. */
|
|
145
|
+
export function unregisterGrassPreset(name) {
|
|
146
|
+
const id = normalizeGrassPresetId(name);
|
|
147
|
+
if (!id || BUILT_IN_GRASS_PRESET_IDS.has(id)) return false;
|
|
148
|
+
return grassPresetRegistry.delete(id);
|
|
149
|
+
}
|
|
150
|
+
|
|
123
151
|
/**
|
|
124
152
|
* Validates, clamps, and merges partial grass options over
|
|
125
153
|
* {@link DEFAULT_GRASS_SETTINGS}. Unknown keys are ignored; malformed values
|
|
@@ -132,7 +160,7 @@ export function getGrassPresetOptions() {
|
|
|
132
160
|
* @returns {Object} A complete, plain grass settings object.
|
|
133
161
|
*/
|
|
134
162
|
export function createGrassSettings(options = {}) {
|
|
135
|
-
const source = cleanObject(options);
|
|
163
|
+
const source = typeof options === 'string' ? { preset: options } : cleanObject(options);
|
|
136
164
|
const presetSettings = grassPresetRegistry.get(source.preset)?.settings;
|
|
137
165
|
const base = presetSettings ? { ...DEFAULT_GRASS_SETTINGS, ...presetSettings } : DEFAULT_GRASS_SETTINGS;
|
|
138
166
|
return {
|
|
@@ -145,6 +173,7 @@ export function createGrassSettings(options = {}) {
|
|
|
145
173
|
cloudShadowStrength: finiteNumber(source.cloudShadowStrength, base.cloudShadowStrength, { min: 0, max: 1 }),
|
|
146
174
|
cloudShadowVelocity: vectorArray(source.cloudShadowVelocity, base.cloudShadowVelocity, 2),
|
|
147
175
|
gustFrequency: finiteNumber(source.gustFrequency, base.gustFrequency, { min: 0 }),
|
|
176
|
+
gustResponse: finiteNumber(source.gustResponse, base.gustResponse, { min: 0 }),
|
|
148
177
|
gustSpeed: finiteNumber(source.gustSpeed, base.gustSpeed, { min: 0 }),
|
|
149
178
|
pushRadius: finiteNumber(source.pushRadius, base.pushRadius, { min: 0 }),
|
|
150
179
|
shadowStrength: finiteNumber(source.shadowStrength, base.shadowStrength, { min: 0, max: 1 }),
|
|
@@ -154,6 +183,7 @@ export function createGrassSettings(options = {}) {
|
|
|
154
183
|
sunDirection: vectorArray(source.sunDirection, base.sunDirection, 3),
|
|
155
184
|
tipColor: colorArray(source.tipColor, base.tipColor),
|
|
156
185
|
windDirection: vectorArray(source.windDirection, base.windDirection, 2),
|
|
186
|
+
windResponse: finiteNumber(source.windResponse, base.windResponse, { min: 0 }),
|
|
157
187
|
windSpeed: finiteNumber(source.windSpeed, base.windSpeed),
|
|
158
188
|
windStrength: finiteNumber(source.windStrength, base.windStrength, { min: 0 }),
|
|
159
189
|
};
|
|
@@ -171,24 +201,48 @@ export const GRASS_SETTING_GROUPS = Object.freeze([
|
|
|
171
201
|
label: 'Blades',
|
|
172
202
|
}),
|
|
173
203
|
Object.freeze({
|
|
174
|
-
description: '
|
|
204
|
+
description: 'Asset-level flexibility: how this grass responds when a scene supplies wind and gusts.',
|
|
175
205
|
id: 'wind',
|
|
176
|
-
label: '
|
|
206
|
+
label: 'Motion',
|
|
207
|
+
}),
|
|
208
|
+
Object.freeze({
|
|
209
|
+
description: "The blades' coordinated base, tip, and material shadow colors — the grass's identity, whatever the scene lighting does. Magical blue grass welcome.",
|
|
210
|
+
id: 'palette',
|
|
211
|
+
label: 'Palette',
|
|
177
212
|
}),
|
|
178
213
|
Object.freeze({
|
|
179
|
-
description: '
|
|
214
|
+
description: 'How the blades RESPOND to scene light — e.g. the backlit glow on blades between the camera and the sun.',
|
|
180
215
|
id: 'lighting',
|
|
181
216
|
label: 'Lighting',
|
|
182
217
|
}),
|
|
183
218
|
Object.freeze({
|
|
184
|
-
description: '
|
|
219
|
+
description: 'Grass-material shadow strength and palette tint. The renderer and cloud-shadow fields themselves come from the scene.',
|
|
185
220
|
id: 'shadows',
|
|
186
221
|
label: 'Shadows',
|
|
187
222
|
}),
|
|
188
223
|
Object.freeze({
|
|
189
|
-
description: '
|
|
224
|
+
description: 'Current sun direction/color and sky color supplied by the scene at runtime.',
|
|
225
|
+
id: 'sceneLight',
|
|
226
|
+
label: 'Scene Light',
|
|
227
|
+
scene: true,
|
|
228
|
+
}),
|
|
229
|
+
Object.freeze({
|
|
230
|
+
description: 'Current world wind and gust field supplied by weather or another scene system.',
|
|
231
|
+
id: 'sceneWind',
|
|
232
|
+
label: 'Scene Wind',
|
|
233
|
+
scene: true,
|
|
234
|
+
}),
|
|
235
|
+
Object.freeze({
|
|
236
|
+
description: 'Current drifting cloud-shadow field shared across terrain, water, and vegetation.',
|
|
237
|
+
id: 'sceneCloudShadow',
|
|
238
|
+
label: 'Cloud Field',
|
|
239
|
+
scene: true,
|
|
240
|
+
}),
|
|
241
|
+
Object.freeze({
|
|
242
|
+
description: 'Current push target and influence radius supplied per scene or grass instance.',
|
|
190
243
|
id: 'interaction',
|
|
191
244
|
label: 'Interaction',
|
|
245
|
+
scene: true,
|
|
192
246
|
}),
|
|
193
247
|
]);
|
|
194
248
|
|
|
@@ -206,37 +260,51 @@ const GRASS_FIELD_DEFINITIONS = Object.freeze({
|
|
|
206
260
|
},
|
|
207
261
|
},
|
|
208
262
|
wind: {
|
|
263
|
+
windResponse: {
|
|
264
|
+
description: 'Asset flexibility multiplier applied to the current scene wind strength. 1 preserves the authored baseline; 0 keeps blades still.',
|
|
265
|
+
label: 'Wind Response',
|
|
266
|
+
range: { max: 8, min: 0, step: 0.01 },
|
|
267
|
+
type: 'number',
|
|
268
|
+
},
|
|
269
|
+
gustResponse: {
|
|
270
|
+
description: 'How strongly this grass follows gust bands relative to its regular wind sway.',
|
|
271
|
+
label: 'Gust Response',
|
|
272
|
+
range: { max: 4, min: 0, step: 0.01 },
|
|
273
|
+
type: 'number',
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
sceneWind: {
|
|
209
277
|
windDirection: {
|
|
210
|
-
description: '
|
|
278
|
+
description: 'Current horizontal (XZ) heading the world wind blows toward.',
|
|
211
279
|
label: 'Wind Direction',
|
|
212
280
|
type: 'vector2',
|
|
213
281
|
},
|
|
214
282
|
windSpeed: {
|
|
215
|
-
description: '
|
|
283
|
+
description: 'Current temporal speed of the world wind.',
|
|
216
284
|
label: 'Wind Speed',
|
|
217
285
|
range: { max: 4, min: 0, step: 0.01 },
|
|
218
286
|
type: 'number',
|
|
219
287
|
},
|
|
220
288
|
windStrength: {
|
|
221
|
-
description: '
|
|
289
|
+
description: 'Current world wind amplitude before the asset response multiplier.',
|
|
222
290
|
label: 'Wind Strength',
|
|
223
291
|
range: { max: 1, min: 0, step: 0.005 },
|
|
224
292
|
type: 'number',
|
|
225
293
|
},
|
|
226
294
|
gustFrequency: {
|
|
227
|
-
description: '
|
|
295
|
+
description: 'Current spatial frequency of the world gust bands.',
|
|
228
296
|
label: 'Gust Frequency',
|
|
229
297
|
range: { max: 2, min: 0, step: 0.01 },
|
|
230
298
|
type: 'number',
|
|
231
299
|
},
|
|
232
300
|
gustSpeed: {
|
|
233
|
-
description: '
|
|
301
|
+
description: 'Current travel speed of the world gust bands.',
|
|
234
302
|
label: 'Gust Speed',
|
|
235
303
|
range: { max: 6, min: 0, step: 0.01 },
|
|
236
304
|
type: 'number',
|
|
237
305
|
},
|
|
238
306
|
},
|
|
239
|
-
|
|
307
|
+
palette: {
|
|
240
308
|
baseColor: {
|
|
241
309
|
description: 'Blade color at the root.',
|
|
242
310
|
label: 'Base Color',
|
|
@@ -247,6 +315,16 @@ const GRASS_FIELD_DEFINITIONS = Object.freeze({
|
|
|
247
315
|
label: 'Tip Color',
|
|
248
316
|
type: 'color',
|
|
249
317
|
},
|
|
318
|
+
},
|
|
319
|
+
lighting: {
|
|
320
|
+
backlitStrength: {
|
|
321
|
+
description: 'Translucent backlight boost when the camera looks toward the sun through the blades.',
|
|
322
|
+
label: 'Backlit Strength',
|
|
323
|
+
range: { max: 2, min: 0, step: 0.01 },
|
|
324
|
+
type: 'number',
|
|
325
|
+
},
|
|
326
|
+
},
|
|
327
|
+
sceneLight: {
|
|
250
328
|
sunDirection: {
|
|
251
329
|
description: 'World-space direction toward the sun (normalized on apply). Match your main directional light.',
|
|
252
330
|
label: 'Sun Direction',
|
|
@@ -262,12 +340,6 @@ const GRASS_FIELD_DEFINITIONS = Object.freeze({
|
|
|
262
340
|
label: 'Sky Color',
|
|
263
341
|
type: 'color',
|
|
264
342
|
},
|
|
265
|
-
backlitStrength: {
|
|
266
|
-
description: 'Translucent backlight boost when the camera looks toward the sun through the blades.',
|
|
267
|
-
label: 'Backlit Strength',
|
|
268
|
-
range: { max: 2, min: 0, step: 0.01 },
|
|
269
|
-
type: 'number',
|
|
270
|
-
},
|
|
271
343
|
},
|
|
272
344
|
shadows: {
|
|
273
345
|
shadowStrength: {
|
|
@@ -277,37 +349,39 @@ const GRASS_FIELD_DEFINITIONS = Object.freeze({
|
|
|
277
349
|
type: 'number',
|
|
278
350
|
},
|
|
279
351
|
shadowTint: {
|
|
280
|
-
description: '
|
|
352
|
+
description: 'Grass material color approached in full scene or cloud shadow. Palette presets set it with base/tip colors; the IP-wide vegetation shadow treatment still layers over it.',
|
|
281
353
|
label: 'Shadow Tint',
|
|
282
354
|
type: 'color',
|
|
283
355
|
},
|
|
356
|
+
},
|
|
357
|
+
sceneCloudShadow: {
|
|
284
358
|
cloudShadowStrength: {
|
|
285
|
-
description: '
|
|
359
|
+
description: 'Current strength of the shared procedural cloud-shadow field. 0 disables it.',
|
|
286
360
|
label: 'Cloud Shadow Strength',
|
|
287
361
|
range: { max: 1, min: 0, step: 0.01 },
|
|
288
362
|
type: 'number',
|
|
289
363
|
},
|
|
290
364
|
cloudShadowCoverage: {
|
|
291
|
-
description: '
|
|
365
|
+
description: 'Current fraction of the world covered by cloud shadow.',
|
|
292
366
|
label: 'Cloud Shadow Coverage',
|
|
293
367
|
range: { max: 1, min: 0, step: 0.01 },
|
|
294
368
|
type: 'number',
|
|
295
369
|
},
|
|
296
370
|
cloudShadowScale: {
|
|
297
|
-
description: '
|
|
371
|
+
description: 'Current world-to-noise scale of the shared cloud pattern.',
|
|
298
372
|
label: 'Cloud Shadow Scale',
|
|
299
373
|
range: { max: 0.1, min: 0.001, step: 0.001 },
|
|
300
374
|
type: 'number',
|
|
301
375
|
},
|
|
302
376
|
cloudShadowVelocity: {
|
|
303
|
-
description: '
|
|
377
|
+
description: 'Current cloud-shadow drift in noise-space units per second.',
|
|
304
378
|
label: 'Cloud Shadow Velocity',
|
|
305
379
|
type: 'vector2',
|
|
306
380
|
},
|
|
307
381
|
},
|
|
308
382
|
interaction: {
|
|
309
383
|
pushRadius: {
|
|
310
|
-
description: '
|
|
384
|
+
description: 'Current radius in meters around the scene push target.',
|
|
311
385
|
label: 'Push Radius',
|
|
312
386
|
range: { max: 3, min: 0, step: 0.01 },
|
|
313
387
|
type: 'number',
|
|
@@ -327,7 +401,7 @@ function createGrassFieldMetadata(group, key, field) {
|
|
|
327
401
|
optionLabels: field.optionLabels ?? null,
|
|
328
402
|
options: field.options ?? null,
|
|
329
403
|
range: field.range ?? null,
|
|
330
|
-
serializable: field.serializable ??
|
|
404
|
+
serializable: field.serializable ?? !group.scene,
|
|
331
405
|
type: field.type,
|
|
332
406
|
});
|
|
333
407
|
}
|
|
@@ -352,6 +426,135 @@ export const GRASS_SETTING_FIELD_SCHEMA = Object.freeze(
|
|
|
352
426
|
),
|
|
353
427
|
);
|
|
354
428
|
|
|
429
|
+
const GRASS_FIELDS_BY_KEY = Object.freeze(Object.fromEntries(
|
|
430
|
+
Object.values(GRASS_SETTING_FIELD_SCHEMA)
|
|
431
|
+
.flatMap((fields) => Object.entries(fields)),
|
|
432
|
+
));
|
|
433
|
+
|
|
434
|
+
function normalizeGrassPresetId(value) {
|
|
435
|
+
return String(value ?? '').trim();
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
function collectTopLevelGrassSettings(source) {
|
|
439
|
+
const input = cleanObject(source);
|
|
440
|
+
return Object.fromEntries(
|
|
441
|
+
Object.keys(GRASS_FIELDS_BY_KEY)
|
|
442
|
+
.filter((key) => input[key] !== undefined)
|
|
443
|
+
.map((key) => [key, input[key]]),
|
|
444
|
+
);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function collectGrassPresetWarnings(settings = {}) {
|
|
448
|
+
const warnings = [];
|
|
449
|
+
for (const key of Object.keys(cleanObject(settings))) {
|
|
450
|
+
const field = GRASS_FIELDS_BY_KEY[key];
|
|
451
|
+
if (!field) warnings.push(`Unknown grass setting "${key}" was ignored.`);
|
|
452
|
+
else if (!field.serializable) {
|
|
453
|
+
warnings.push(`Grass setting "${key}" is scene-owned and was not stored in the preset.`);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
return warnings;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* Normalizes a grass preset into complete JSON-safe product settings. Scene
|
|
461
|
+
* inputs supplied by the active sun/sky rig are deliberately excluded.
|
|
462
|
+
*/
|
|
463
|
+
export function sanitizeGrassPresetSettings(settings = {}) {
|
|
464
|
+
const knownSettings = collectTopLevelGrassSettings(settings);
|
|
465
|
+
const normalized = createGrassSettings(knownSettings);
|
|
466
|
+
return Object.fromEntries(
|
|
467
|
+
Object.entries(GRASS_FIELDS_BY_KEY)
|
|
468
|
+
.filter(([, field]) => field.serializable)
|
|
469
|
+
.map(([key]) => [key, normalized[key]]),
|
|
470
|
+
);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
function migrateGrassPresetDocument(input) {
|
|
474
|
+
const source = cleanObject(input);
|
|
475
|
+
const numericVersion = Number(source.version ?? source.schemaVersion ?? 0);
|
|
476
|
+
const version = Number.isFinite(numericVersion) ? Math.round(numericVersion) : 0;
|
|
477
|
+
if (version > GRASS_PRESET_SCHEMA_VERSION) return { ...source, version };
|
|
478
|
+
const nestedSettings = cleanObject(source.settings);
|
|
479
|
+
const settings = Object.keys(nestedSettings).length > 0
|
|
480
|
+
? { ...nestedSettings }
|
|
481
|
+
: collectTopLevelGrassSettings(source);
|
|
482
|
+
|
|
483
|
+
// Schema v1 treated windStrength as a portable grass value. Preserve that
|
|
484
|
+
// authored look by converting it to the v2 species response relative to the
|
|
485
|
+
// historical world-wind default. Keep the original key so validation also
|
|
486
|
+
// explains that the live scene field is no longer stored. A v2 document is
|
|
487
|
+
// never reinterpreted this way.
|
|
488
|
+
if (version <= 1 && settings.windResponse === undefined
|
|
489
|
+
&& Number.isFinite(Number(settings.windStrength))) {
|
|
490
|
+
settings.windResponse = Math.max(Number(settings.windStrength), 0)
|
|
491
|
+
/ DEFAULT_GRASS_SETTINGS.windStrength;
|
|
492
|
+
}
|
|
493
|
+
return {
|
|
494
|
+
description: source.description ?? '',
|
|
495
|
+
id: source.id ?? source.name ?? source.preset ?? '',
|
|
496
|
+
label: source.label ?? source.title ?? source.name ?? source.id ?? '',
|
|
497
|
+
settings,
|
|
498
|
+
type: source.type ?? GRASS_PRESET_DOCUMENT_TYPE,
|
|
499
|
+
version: GRASS_PRESET_SCHEMA_VERSION,
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/** Validates and normalizes a portable grass preset document. Never throws. */
|
|
504
|
+
export function validateGrassPresetDocument(input) {
|
|
505
|
+
if (cleanObject(input).type !== undefined && input.type !== GRASS_PRESET_DOCUMENT_TYPE) {
|
|
506
|
+
return {
|
|
507
|
+
errors: [`Grass preset type must be "${GRASS_PRESET_DOCUMENT_TYPE}".`],
|
|
508
|
+
ok: false,
|
|
509
|
+
value: null,
|
|
510
|
+
warnings: [],
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
return validateSettingsPresetDocument(input, {
|
|
514
|
+
collectWarnings: collectGrassPresetWarnings,
|
|
515
|
+
documentType: GRASS_PRESET_DOCUMENT_TYPE,
|
|
516
|
+
migrateDocument: migrateGrassPresetDocument,
|
|
517
|
+
normalizeId: normalizeGrassPresetId,
|
|
518
|
+
sanitizeSettings: sanitizeGrassPresetSettings,
|
|
519
|
+
schemaVersion: GRASS_PRESET_SCHEMA_VERSION,
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
/** Parses JSON text or an object into a validated grass preset document. */
|
|
524
|
+
export function parseGrassPresetDocument(input) {
|
|
525
|
+
return parsePresetDocument(input, validateGrassPresetDocument, {
|
|
526
|
+
invalidJsonLabel: 'grass preset',
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/** Creates a canonical, versioned grass preset document. */
|
|
531
|
+
export function createGrassPresetDocument(id, definition = {}) {
|
|
532
|
+
return createSettingsPresetDocument(id, definition, {
|
|
533
|
+
collectSettings: (source) => source.settings ?? collectTopLevelGrassSettings(source),
|
|
534
|
+
documentType: GRASS_PRESET_DOCUMENT_TYPE,
|
|
535
|
+
schemaVersion: GRASS_PRESET_SCHEMA_VERSION,
|
|
536
|
+
validateDocument: validateGrassPresetDocument,
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
/** Serializes a grass preset id/definition or document-like object as JSON. */
|
|
541
|
+
export function serializeGrassPreset(idOrDocument, definition = {}, { pretty = true } = {}) {
|
|
542
|
+
return serializePresetDocument(idOrDocument, definition, {
|
|
543
|
+
argumentCount: arguments.length,
|
|
544
|
+
createDocument: createGrassPresetDocument,
|
|
545
|
+
pretty,
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
/** Registers a portable grass document, overwriting an existing id by default. */
|
|
550
|
+
export function registerSerializedGrassPreset(input, options = {}) {
|
|
551
|
+
const result = parseGrassPresetDocument(input);
|
|
552
|
+
if (!result.ok) throw new Error(result.errors.join(' '));
|
|
553
|
+
return registerGrassPreset(result.value.id, result.value, {
|
|
554
|
+
overwrite: options.overwrite ?? true,
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
|
|
355
558
|
// Dense instanced grass: procedural tapered blades with wind sway and a
|
|
356
559
|
// push-away radius around a character. One draw call for the whole field;
|
|
357
560
|
// emitting is a one-time attribute fill, animation is entirely in the vertex
|
|
@@ -370,7 +573,7 @@ export const GRASS_SETTING_FIELD_SCHEMA = Object.freeze(
|
|
|
370
573
|
// existing callers keep working unchanged.
|
|
371
574
|
export class StylizedGrassField extends THREE.Mesh {
|
|
372
575
|
constructor(options = {}) {
|
|
373
|
-
const { placements = [] } = cleanObject(options);
|
|
576
|
+
const { placements = [], vegetationShader = null } = cleanObject(options);
|
|
374
577
|
const settings = createGrassSettings(options);
|
|
375
578
|
const { bladeHeightRange, bladeWidthRange } = settings;
|
|
376
579
|
|
|
@@ -401,7 +604,7 @@ export class StylizedGrassField extends THREE.Mesh {
|
|
|
401
604
|
geometry.instanceCount = placements.length;
|
|
402
605
|
geometry.boundingSphere = new THREE.Sphere(new THREE.Vector3(), 1e5);
|
|
403
606
|
|
|
404
|
-
const material = createGrassNodeMaterial(settings);
|
|
607
|
+
const material = createGrassNodeMaterial(settings, vegetationShader);
|
|
405
608
|
setSrgbColor(material.uniforms.uBaseColor.value, settings.baseColor);
|
|
406
609
|
setSrgbColor(material.uniforms.uTipColor.value, settings.tipColor);
|
|
407
610
|
setSrgbColor(material.uniforms.uSunColor.value, settings.sunColor);
|
|
@@ -418,7 +621,8 @@ export class StylizedGrassField extends THREE.Mesh {
|
|
|
418
621
|
|
|
419
622
|
/**
|
|
420
623
|
* Runtime re-tune: merges `options` into the current settings and pushes
|
|
421
|
-
* every material-driven value (
|
|
624
|
+
* every material-driven value (asset response, current scene fields,
|
|
625
|
+
* palette, sun, shadows, and push radius)
|
|
422
626
|
* into the uniforms. `bladeHeightRange` / `bladeWidthRange` are baked into
|
|
423
627
|
* the instance attributes at construction and are construction-only; new
|
|
424
628
|
* values are stored but do not reshape existing blades.
|
|
@@ -441,7 +645,9 @@ export class StylizedGrassField extends THREE.Mesh {
|
|
|
441
645
|
uniforms.uWindSpeed.value = settings.windSpeed;
|
|
442
646
|
uniforms.uWindStrength.value = settings.windStrength;
|
|
443
647
|
uniforms.uGustFrequency.value = settings.gustFrequency;
|
|
648
|
+
uniforms.uGustResponse.value = settings.gustResponse;
|
|
444
649
|
uniforms.uGustSpeed.value = settings.gustSpeed;
|
|
650
|
+
uniforms.uWindResponse.value = settings.windResponse;
|
|
445
651
|
uniforms.uPushRadius.value = settings.pushRadius;
|
|
446
652
|
uniforms.uBacklitStrength.value = settings.backlitStrength;
|
|
447
653
|
uniforms.uCloudShadowStrength.value = settings.cloudShadowStrength;
|
|
@@ -459,22 +665,26 @@ export class StylizedGrassField extends THREE.Mesh {
|
|
|
459
665
|
}
|
|
460
666
|
|
|
461
667
|
setWind({ direction, speed, strength, gustFrequency, gustSpeed } = {}) {
|
|
462
|
-
this.
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
});
|
|
668
|
+
const uniforms = this.material.uniforms;
|
|
669
|
+
if (direction !== undefined) {
|
|
670
|
+
const next = vectorArray(direction, this.settings.windDirection, 2);
|
|
671
|
+
uniforms.uWindDirection.value.set(next[0], next[1]);
|
|
672
|
+
}
|
|
673
|
+
if (speed !== undefined) uniforms.uWindSpeed.value = finiteNumber(speed, uniforms.uWindSpeed.value);
|
|
674
|
+
if (strength !== undefined) uniforms.uWindStrength.value = finiteNumber(strength, uniforms.uWindStrength.value, { min: 0 });
|
|
675
|
+
if (gustFrequency !== undefined) uniforms.uGustFrequency.value = finiteNumber(gustFrequency, uniforms.uGustFrequency.value, { min: 0 });
|
|
676
|
+
if (gustSpeed !== undefined) uniforms.uGustSpeed.value = finiteNumber(gustSpeed, uniforms.uGustSpeed.value, { min: 0 });
|
|
469
677
|
return this;
|
|
470
678
|
}
|
|
471
679
|
|
|
472
680
|
setSun({ direction, color, sky } = {}) {
|
|
473
|
-
this.
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
}
|
|
681
|
+
const uniforms = this.material.uniforms;
|
|
682
|
+
if (direction !== undefined) {
|
|
683
|
+
const next = vectorArray(direction, this.settings.sunDirection, 3);
|
|
684
|
+
uniforms.uSunDirection.value.set(...next).normalize();
|
|
685
|
+
}
|
|
686
|
+
if (color !== undefined) setSrgbColor(uniforms.uSunColor.value, colorArray(color, this.settings.sunColor));
|
|
687
|
+
if (sky !== undefined) setSrgbColor(uniforms.uSkyColor.value, colorArray(sky, this.settings.skyColor));
|
|
478
688
|
return this;
|
|
479
689
|
}
|
|
480
690
|
|
|
@@ -491,15 +701,33 @@ export class StylizedGrassField extends THREE.Mesh {
|
|
|
491
701
|
// Drifting procedural cloud shadows over the field. strength 0 disables.
|
|
492
702
|
// velocity is uv-space drift per second (worldDrift = velocity / scale).
|
|
493
703
|
setCloudShadow({ strength, coverage, scale, velocity } = {}) {
|
|
494
|
-
this.
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
704
|
+
const uniforms = this.material.uniforms;
|
|
705
|
+
if (strength !== undefined) uniforms.uCloudShadowStrength.value = finiteNumber(strength, uniforms.uCloudShadowStrength.value, { min: 0, max: 1 });
|
|
706
|
+
if (coverage !== undefined) uniforms.uCloudShadowCoverage.value = finiteNumber(coverage, uniforms.uCloudShadowCoverage.value, { min: 0, max: 1 });
|
|
707
|
+
if (scale !== undefined) uniforms.uCloudShadowScale.value = finiteNumber(scale, uniforms.uCloudShadowScale.value, { min: 0.0001 });
|
|
708
|
+
if (velocity !== undefined) {
|
|
709
|
+
const next = vectorArray(velocity, this.settings.cloudShadowVelocity, 2);
|
|
710
|
+
uniforms.uCloudShadowVelocity.value.set(next[0], next[1]);
|
|
711
|
+
}
|
|
500
712
|
return this;
|
|
501
713
|
}
|
|
502
714
|
|
|
715
|
+
/** Current world surface state. Responses remain owned by the shader/material profile. */
|
|
716
|
+
setSurfaceWeather({ wetness, snowCover } = {}) {
|
|
717
|
+
const uniforms = this.material.uniforms;
|
|
718
|
+
if (uniforms.uWetness && wetness !== undefined) {
|
|
719
|
+
uniforms.uWetness.value = finiteNumber(wetness, uniforms.uWetness.value, { min: 0, max: 1 });
|
|
720
|
+
}
|
|
721
|
+
if (uniforms.uSnowCover && snowCover !== undefined) {
|
|
722
|
+
uniforms.uSnowCover.value = finiteNumber(snowCover, uniforms.uSnowCover.value, { min: 0, max: 1 });
|
|
723
|
+
}
|
|
724
|
+
return this;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
setVegetationShader(profile) {
|
|
728
|
+
return applyVegetationShader(this, profile);
|
|
729
|
+
}
|
|
730
|
+
|
|
503
731
|
// Collapse blades between start and end meters from the camera so distant,
|
|
504
732
|
// fog-swallowed grass stops costing fill rate. Pass nothing to disable.
|
|
505
733
|
setDistanceFade({ start = 1e6, end } = {}) {
|
|
@@ -515,6 +743,13 @@ export class StylizedGrassField extends THREE.Mesh {
|
|
|
515
743
|
return this;
|
|
516
744
|
}
|
|
517
745
|
|
|
746
|
+
/** Sets the current scene/instance interaction radius without editing the asset preset. */
|
|
747
|
+
setPushRadius(radius) {
|
|
748
|
+
const uniforms = this.material.uniforms;
|
|
749
|
+
uniforms.uPushRadius.value = finiteNumber(radius, uniforms.uPushRadius.value, { min: 0 });
|
|
750
|
+
return this;
|
|
751
|
+
}
|
|
752
|
+
|
|
518
753
|
update(delta) {
|
|
519
754
|
const uniforms = this.material.uniforms;
|
|
520
755
|
uniforms.uTime.value += Math.min(Math.max(delta ?? 0.016, 0), 0.1);
|
|
@@ -15,6 +15,11 @@ import {
|
|
|
15
15
|
setCanopyWind,
|
|
16
16
|
tickCanopyTime,
|
|
17
17
|
} from './stylizedTreeFoliage.js';
|
|
18
|
+
import {
|
|
19
|
+
createWoodySurfaceNodeMaterial,
|
|
20
|
+
setWoodySurfaceSun,
|
|
21
|
+
} from '../shaders-tsl/woody-surface.js';
|
|
22
|
+
import { applyVegetationShader } from './vegetationShaders.js';
|
|
18
23
|
|
|
19
24
|
// Modern anime-style stylized trees, fully parameterized for drop-in use:
|
|
20
25
|
//
|
|
@@ -1980,7 +1985,7 @@ function sameSettingValue(a, b) {
|
|
|
1980
1985
|
// skeleton — createTreeSkeleton options (limbsPerBlob, limbRadius, ...)
|
|
1981
1986
|
// canopy — createTreeFoliageGeometry options (blobs, cardCount, ...)
|
|
1982
1987
|
// foliage — createTreeFoliageMaterials options (cutoff, wind, sun, ...)
|
|
1983
|
-
// trunkMaterial— any THREE material; default is a
|
|
1988
|
+
// trunkMaterial— any THREE material; default is a stylized woody material
|
|
1984
1989
|
export class StylizedTree extends THREE.Group {
|
|
1985
1990
|
constructor(options = {}) {
|
|
1986
1991
|
super();
|
|
@@ -2009,6 +2014,7 @@ export class StylizedTree extends THREE.Group {
|
|
|
2009
2014
|
// collar (a complete tree meets the ground).
|
|
2010
2015
|
const {
|
|
2011
2016
|
trunkMaterial = null,
|
|
2017
|
+
vegetationShader = null,
|
|
2012
2018
|
branchSpines = [],
|
|
2013
2019
|
extraBlobs = [],
|
|
2014
2020
|
extraAttachments = [],
|
|
@@ -2373,12 +2379,18 @@ export class StylizedTree extends THREE.Group {
|
|
|
2373
2379
|
? { leafMap: leafSpriteForShape(leafShape) }
|
|
2374
2380
|
: {}),
|
|
2375
2381
|
...settings.foliage,
|
|
2382
|
+
vegetationShader,
|
|
2376
2383
|
});
|
|
2377
2384
|
|
|
2378
2385
|
this.trunkMesh = new THREE.Mesh(
|
|
2379
2386
|
trunkGeometry,
|
|
2380
|
-
trunkMaterial ??
|
|
2387
|
+
trunkMaterial ?? createWoodySurfaceNodeMaterial({
|
|
2388
|
+
color: 0xc9ab8a,
|
|
2389
|
+
height: settings.trunk.height,
|
|
2390
|
+
vegetationShader,
|
|
2391
|
+
}),
|
|
2381
2392
|
);
|
|
2393
|
+
this.trunkMesh.userData.toonlabVegetationRole = 'woodySurface';
|
|
2382
2394
|
this.trunkMesh.castShadow = true;
|
|
2383
2395
|
this.trunkMesh.receiveShadow = trunkReceiveShadow;
|
|
2384
2396
|
|
|
@@ -2449,6 +2461,11 @@ export class StylizedTree extends THREE.Group {
|
|
|
2449
2461
|
uniforms.uCloudShadowScale.value = foliage.cloudShadowScale;
|
|
2450
2462
|
uniforms.uCloudShadowVelocity.value.set(
|
|
2451
2463
|
foliage.cloudShadowVelocity[0], foliage.cloudShadowVelocity[1]);
|
|
2464
|
+
setWoodySurfaceSun(this.trunkMesh.material, {
|
|
2465
|
+
color: foliage.sunColor,
|
|
2466
|
+
direction: foliage.sunDirection,
|
|
2467
|
+
sky: foliage.skyColor,
|
|
2468
|
+
});
|
|
2452
2469
|
|
|
2453
2470
|
// Re-derive the three-tone palette from the (possibly updated) canopy
|
|
2454
2471
|
// color/palette pins. Constructor-only foliage.color/palette overrides
|
|
@@ -2470,6 +2487,7 @@ export class StylizedTree extends THREE.Group {
|
|
|
2470
2487
|
|
|
2471
2488
|
setSun(options) {
|
|
2472
2489
|
setCanopySun(this.canopyMesh.material.uniforms, options ?? {});
|
|
2490
|
+
setWoodySurfaceSun(this.trunkMesh.material, options ?? {});
|
|
2473
2491
|
return this;
|
|
2474
2492
|
}
|
|
2475
2493
|
|
|
@@ -2490,6 +2508,24 @@ export class StylizedTree extends THREE.Group {
|
|
|
2490
2508
|
return this;
|
|
2491
2509
|
}
|
|
2492
2510
|
|
|
2511
|
+
/** Current world surface state; material response coefficients stay in the vegetation shader profile. */
|
|
2512
|
+
setSurfaceWeather({ wetness, snowCover } = {}) {
|
|
2513
|
+
for (const material of [this.canopyMesh?.material, this.trunkMesh?.material]) {
|
|
2514
|
+
const uniforms = material?.uniforms;
|
|
2515
|
+
if (uniforms?.uWetness && wetness !== undefined) {
|
|
2516
|
+
uniforms.uWetness.value = THREE.MathUtils.clamp(Number(wetness) || 0, 0, 1);
|
|
2517
|
+
}
|
|
2518
|
+
if (uniforms?.uSnowCover && snowCover !== undefined) {
|
|
2519
|
+
uniforms.uSnowCover.value = THREE.MathUtils.clamp(Number(snowCover) || 0, 0, 1);
|
|
2520
|
+
}
|
|
2521
|
+
}
|
|
2522
|
+
return this;
|
|
2523
|
+
}
|
|
2524
|
+
|
|
2525
|
+
setVegetationShader(profile) {
|
|
2526
|
+
return applyVegetationShader(this, profile);
|
|
2527
|
+
}
|
|
2528
|
+
|
|
2493
2529
|
update(delta) {
|
|
2494
2530
|
tickCanopyTime(this.canopyMesh.material.uniforms, delta);
|
|
2495
2531
|
return this;
|