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:
241
src/app/App.tsx
241
src/app/App.tsx
@@ -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 : (
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user