Feat: Add foliage wind settings for advanced rendering

Implements controls and logic for managing foliage wind parameters (enablement, strength, speed, and direction) in the advanced rendering settings. Updates serialization tests to handle these new fields and migration from older document versions.
This commit is contained in:
2026-05-21 06:06:32 +02:00
parent 18ff8464d6
commit c7abf10930
2 changed files with 269 additions and 1 deletions

View File

@@ -208,8 +208,12 @@ import {
MAX_ADVANCED_RENDERING_LENS_FLARE_GHOST_COUNT,
MAX_FOLIAGE_QUALITY_DENSITY_MULTIPLIER,
MAX_FOLIAGE_QUALITY_MAX_DISTANCE_MULTIPLIER,
MAX_FOLIAGE_QUALITY_WIND_SPEED,
MAX_FOLIAGE_QUALITY_WIND_STRENGTH,
MIN_FOLIAGE_QUALITY_DENSITY_MULTIPLIER,
MIN_FOLIAGE_QUALITY_MAX_DISTANCE_MULTIPLIER,
MIN_FOLIAGE_QUALITY_WIND_SPEED,
MIN_FOLIAGE_QUALITY_WIND_STRENGTH,
areWorldSettingsEqual,
changeWorldBackgroundMode,
cloneWorldSettings,
@@ -1267,6 +1271,12 @@ function clampNumber(value: number, min: number, max: number): number {
return Math.min(max, Math.max(min, value));
}
function normalizeDegrees(value: number): number {
const normalized = value % 360;
return normalized < 0 ? normalized + 360 : normalized;
}
function assertAdvancedRenderingDistanceFogRange(
distanceFog: AdvancedRenderingDistanceFogSettings
) {
@@ -3820,6 +3830,26 @@ export function App({
editorState.document.world.advancedRendering.foliage.maxDistanceMultiplier
)
);
const [
advancedRenderingFoliageWindStrengthDraft,
setAdvancedRenderingFoliageWindStrengthDraft
] = useState(
String(editorState.document.world.advancedRendering.foliage.windStrength)
);
const [
advancedRenderingFoliageWindSpeedDraft,
setAdvancedRenderingFoliageWindSpeedDraft
] = useState(
String(editorState.document.world.advancedRendering.foliage.windSpeed)
);
const [
advancedRenderingFoliageWindDirectionDraft,
setAdvancedRenderingFoliageWindDirectionDraft
] = useState(
String(
editorState.document.world.advancedRendering.foliage.windDirectionDegrees
)
);
const [
advancedRenderingAmbientOcclusionIntensityDraft,
setAdvancedRenderingAmbientOcclusionIntensityDraft
@@ -5223,6 +5253,15 @@ export function App({
setAdvancedRenderingFoliageMaxDistanceMultiplierDraft(
String(advancedRendering.foliage.maxDistanceMultiplier)
);
setAdvancedRenderingFoliageWindStrengthDraft(
String(advancedRendering.foliage.windStrength)
);
setAdvancedRenderingFoliageWindSpeedDraft(
String(advancedRendering.foliage.windSpeed)
);
setAdvancedRenderingFoliageWindDirectionDraft(
String(advancedRendering.foliage.windDirectionDegrees)
);
setAdvancedRenderingAmbientOcclusionIntensityDraft(
String(advancedRendering.ambientOcclusion.intensity)
);
@@ -13890,6 +13929,89 @@ export function App({
);
};
const applyAdvancedRenderingFoliageWindEnabled = (enabled: boolean) => {
applyAdvancedRenderingSettings(
"Set foliage wind",
enabled ? "Foliage wind enabled." : "Foliage wind disabled.",
(advancedRendering) => {
advancedRendering.foliage.windEnabled = enabled;
}
);
};
const applyAdvancedRenderingFoliageWindStrength = () => {
try {
const windStrength = clampNumber(
readFiniteNumberDraft(
advancedRenderingFoliageWindStrengthDraft,
"Foliage wind strength"
),
MIN_FOLIAGE_QUALITY_WIND_STRENGTH,
MAX_FOLIAGE_QUALITY_WIND_STRENGTH
);
setAdvancedRenderingFoliageWindStrengthDraft(String(windStrength));
applyAdvancedRenderingSettings(
"Set foliage wind strength",
"Updated the foliage wind strength.",
(advancedRendering) => {
advancedRendering.foliage.windStrength = windStrength;
}
);
} catch (error) {
setStatusMessage(getErrorMessage(error));
}
};
const applyAdvancedRenderingFoliageWindSpeed = () => {
try {
const windSpeed = clampNumber(
readFiniteNumberDraft(
advancedRenderingFoliageWindSpeedDraft,
"Foliage wind speed"
),
MIN_FOLIAGE_QUALITY_WIND_SPEED,
MAX_FOLIAGE_QUALITY_WIND_SPEED
);
setAdvancedRenderingFoliageWindSpeedDraft(String(windSpeed));
applyAdvancedRenderingSettings(
"Set foliage wind speed",
"Updated the foliage wind speed.",
(advancedRendering) => {
advancedRendering.foliage.windSpeed = windSpeed;
}
);
} catch (error) {
setStatusMessage(getErrorMessage(error));
}
};
const applyAdvancedRenderingFoliageWindDirection = () => {
try {
const windDirectionDegrees = normalizeDegrees(
readFiniteNumberDraft(
advancedRenderingFoliageWindDirectionDraft,
"Foliage wind direction"
)
);
setAdvancedRenderingFoliageWindDirectionDraft(
String(windDirectionDegrees)
);
applyAdvancedRenderingSettings(
"Set foliage wind direction",
"Updated the foliage wind direction.",
(advancedRendering) => {
advancedRendering.foliage.windDirectionDegrees =
windDirectionDegrees;
}
);
} catch (error) {
setStatusMessage(getErrorMessage(error));
}
};
const applyAdvancedRenderingShadowsEnabled = (enabled: boolean) => {
applyAdvancedRenderingSettings(
"Set advanced rendering shadows",
@@ -20273,6 +20395,125 @@ export function App({
))}
</select>
</label>
<label className="form-field form-field--toggle">
<span className="label">Wind</span>
<input
type="checkbox"
checked={advancedRendering.foliage.windEnabled}
onChange={(event) =>
applyAdvancedRenderingFoliageWindEnabled(
event.currentTarget.checked
)
}
/>
</label>
<div className="vector-inputs vector-inputs--three">
<label className="form-field">
<span className="label">Strength</span>
<input
className="text-input"
type="number"
min={MIN_FOLIAGE_QUALITY_WIND_STRENGTH}
max={MAX_FOLIAGE_QUALITY_WIND_STRENGTH}
step="0.05"
value={advancedRenderingFoliageWindStrengthDraft}
onChange={(event) =>
setAdvancedRenderingFoliageWindStrengthDraft(
event.currentTarget.value
)
}
onBlur={applyAdvancedRenderingFoliageWindStrength}
onKeyDown={(event) =>
handleDraftVectorKeyDown(
event,
applyAdvancedRenderingFoliageWindStrength
)
}
onKeyUp={(event) =>
handleNumberInputKeyUp(
event,
applyAdvancedRenderingFoliageWindStrength
)
}
onPointerUp={(event) =>
handleNumberInputPointerUp(
event,
applyAdvancedRenderingFoliageWindStrength
)
}
/>
</label>
<label className="form-field">
<span className="label">Speed</span>
<input
className="text-input"
type="number"
min={MIN_FOLIAGE_QUALITY_WIND_SPEED}
max={MAX_FOLIAGE_QUALITY_WIND_SPEED}
step="0.05"
value={advancedRenderingFoliageWindSpeedDraft}
onChange={(event) =>
setAdvancedRenderingFoliageWindSpeedDraft(
event.currentTarget.value
)
}
onBlur={applyAdvancedRenderingFoliageWindSpeed}
onKeyDown={(event) =>
handleDraftVectorKeyDown(
event,
applyAdvancedRenderingFoliageWindSpeed
)
}
onKeyUp={(event) =>
handleNumberInputKeyUp(
event,
applyAdvancedRenderingFoliageWindSpeed
)
}
onPointerUp={(event) =>
handleNumberInputPointerUp(
event,
applyAdvancedRenderingFoliageWindSpeed
)
}
/>
</label>
<label className="form-field">
<span className="label">Direction</span>
<input
className="text-input"
type="number"
min="0"
max="360"
step="1"
value={advancedRenderingFoliageWindDirectionDraft}
onChange={(event) =>
setAdvancedRenderingFoliageWindDirectionDraft(
event.currentTarget.value
)
}
onBlur={applyAdvancedRenderingFoliageWindDirection}
onKeyDown={(event) =>
handleDraftVectorKeyDown(
event,
applyAdvancedRenderingFoliageWindDirection
)
}
onKeyUp={(event) =>
handleNumberInputKeyUp(
event,
applyAdvancedRenderingFoliageWindDirection
)
}
onPointerUp={(event) =>
handleNumberInputPointerUp(
event,
applyAdvancedRenderingFoliageWindDirection
)
}
/>
</label>
</div>
</div>
{!advancedRendering.enabled ? null : (

View File

@@ -1172,7 +1172,7 @@ describe("scene document JSON", () => {
expect(
migratedDocument.splineCorridorJunctions[junction.id]?.shapeMode
).toBe("straight");
expect(LENS_FLARE_SCENE_DOCUMENT_VERSION).toBe(SCENE_DOCUMENT_VERSION);
expect(FOLIAGE_WIND_SCENE_DOCUMENT_VERSION).toBe(SCENE_DOCUMENT_VERSION);
});
it("round-trips spline corridor junctions", () => {
@@ -1865,6 +1865,33 @@ describe("scene document JSON", () => {
);
});
it("migrates v111 scene documents without foliage wind settings to defaults", () => {
const emptyScene = createEmptySceneDocument({
name: "Legacy Foliage Wind Scene"
});
const legacyDocument = JSON.parse(
serializeSceneDocument(emptyScene)
) as Record<string, any>;
const legacyFoliage =
legacyDocument.world.advancedRendering.foliage as Record<
string,
unknown
>;
legacyDocument.version = LENS_FLARE_SCENE_DOCUMENT_VERSION;
delete legacyFoliage.windEnabled;
delete legacyFoliage.windStrength;
delete legacyFoliage.windSpeed;
delete legacyFoliage.windDirectionDegrees;
const migratedDocument = migrateSceneDocument(legacyDocument);
expect(migratedDocument.version).toBe(SCENE_DOCUMENT_VERSION);
expect(migratedDocument.world.advancedRendering.foliage).toEqual(
emptyScene.world.advancedRendering.foliage
);
});
it("defaults missing water reflection mode and clamps legacy foam limits during migration", () => {
const migratedDocument = migrateSceneDocument({
version: SCENE_DOCUMENT_VERSION,