export type RenderScale = "adaptive" | 1 | 1.5 | 2; export type TextureFiltering = 1 | 4 | 16; export type ShadowQuality = "off" | "standard" | "high"; export type AmbientFill = "low" | "authored" | "high"; export interface GraphicsSettings { readonly renderScale: RenderScale; readonly textureFiltering: TextureFiltering; readonly shadowQuality: ShadowQuality; readonly ambientFill: AmbientFill; } export const DEFAULT_GRAPHICS_SETTINGS: GraphicsSettings = Object.freeze({ renderScale: "adaptive", textureFiltering: 1, shadowQuality: "standard", ambientFill: "authored", }); export const RENDER_SCALES: readonly RenderScale[] = ["adaptive", 1, 1.5, 2]; export const TEXTURE_FILTERING_LEVELS: readonly TextureFiltering[] = [1, 4, 16]; export const SHADOW_QUALITIES: readonly ShadowQuality[] = ["off", "standard", "high"]; export const AMBIENT_FILL_LEVELS: readonly AmbientFill[] = ["low", "authored", "high"]; export function normalizeGraphicsSettings( current: GraphicsSettings, patch: Partial, ): GraphicsSettings { return { renderScale: RENDER_SCALES.includes(patch.renderScale as RenderScale) ? patch.renderScale! : current.renderScale, textureFiltering: TEXTURE_FILTERING_LEVELS.includes(patch.textureFiltering as TextureFiltering) ? patch.textureFiltering! : current.textureFiltering, shadowQuality: SHADOW_QUALITIES.includes(patch.shadowQuality as ShadowQuality) ? patch.shadowQuality! : current.shadowQuality, ambientFill: AMBIENT_FILL_LEVELS.includes(patch.ambientFill as AmbientFill) ? patch.ambientFill! : current.ambientFill, }; } export function ambientFillMultiplier(fill: AmbientFill): number { return fill === "low" ? 0.6 : fill === "high" ? 1.4 : 1; } export function nextGraphicsChoice(current: T, choices: readonly T[]): T { return choices[(choices.indexOf(current) + 1) % choices.length]; }