diff --git a/src/components/Sky.tsx b/src/components/Sky.tsx index 935c5a1d..b1505eab 100644 --- a/src/components/Sky.tsx +++ b/src/components/Sky.tsx @@ -1,12 +1,11 @@ -import { Suspense, useMemo, useEffect, useRef } from "react"; +import { Suspense, useMemo } from "react"; import { useQuery } from "@tanstack/react-query"; import { useCubeTexture } from "@react-three/drei"; -import { Color, ShaderMaterial, BackSide, Euler, ShaderChunk } from "three"; +import { Color, ShaderMaterial, BackSide, ShaderChunk } from "three"; import type { TorqueObject } from "../torqueScript"; import { getFloat, getInt, getProperty } from "../mission"; import { useSettings } from "./SettingsProvider"; import { BASE_URL, loadDetailMapList, textureToUrl } from "../loaders"; -import { useThree } from "@react-three/fiber"; import { CloudLayers } from "./CloudLayers"; const FALLBACK_TEXTURE_URL = `${BASE_URL}/black.png`; @@ -46,6 +45,21 @@ if (USE_QUADRATIC_FOG) { installQuadraticFogShader(); } +/** + * Parse a Tribes 2 color string (space-separated RGB or RGBA values 0-1). + * Returns [sRGB Color, linear Color] or undefined if no color string. + */ +function parseColorString( + colorString: string | undefined, +): [Color, Color] | undefined { + if (!colorString) return undefined; + const [r, g, b] = colorString.split(" ").map((s) => parseFloat(s)); + return [ + new Color().setRGB(r, g, b), + new Color().setRGB(r, g, b).convertSRGBToLinear(), + ]; +} + /** * Load a .dml file, used to list the textures for different faces of a skybox. */ @@ -59,13 +73,9 @@ function useDetailMapList(name: string) { export function SkyBox({ materialList, fogColor, - fogNear, - fogFar, }: { materialList: string; fogColor?: Color; - fogNear?: number; - fogFar?: number; }) { const { data: detailMapList } = useDetailMapList(materialList); @@ -93,21 +103,14 @@ export function SkyBox({ const skyBox = useCubeTexture(skyBoxFiles, { path: "" }); - // Create a shader material for the skybox with fog - const materialRef = useRef(null!); - - const hasFog = !!fogColor && fogNear != null && fogFar != null; - const shaderMaterial = useMemo(() => { - if (!hasFog) { - return null; - } - - // Skybox fog blends toward horizon + // Always use a shader to apply the X-axis mirror transformation. + // Optionally blend fog toward the horizon. return new ShaderMaterial({ uniforms: { skybox: { value: skyBox }, - fogColor: { value: fogColor }, + fogColor: { value: fogColor ?? new Color(0, 0, 0) }, + enableFog: { value: !!fogColor }, }, vertexShader: ` varying vec3 vDirection; @@ -121,53 +124,37 @@ export function SkyBox({ fragmentShader: ` uniform samplerCube skybox; uniform vec3 fogColor; + uniform bool enableFog; varying vec3 vDirection; void main() { vec3 direction = normalize(vDirection); - // Swap X and Z to match scene.backgroundRotation used in non-fog path - direction = vec3(direction.z, direction.y, direction.x); + // Swap X and Z, negate X to mirror across X axis + direction = vec3(direction.z, direction.y, -direction.x); vec4 skyColor = textureCube(skybox, direction); - // Fog increases toward and below horizon - // direction.y: -1 = straight down, 0 = horizon, 1 = straight up - // Use smoothstep for gradual transition (matches Three.js linear fog feel) - float fogFactor = 1.0 - smoothstep(-0.1, 0.5, direction.y); - - vec3 finalColor = mix(skyColor.rgb, fogColor, fogFactor); - gl_FragColor = vec4(finalColor, 1.0); + if (enableFog) { + // Fog increases toward and below horizon + // direction.y: -1 = straight down, 0 = horizon, 1 = straight up + // Use smoothstep for gradual transition (matches Three.js linear fog feel) + float fogFactor = 1.0 - smoothstep(-0.1, 0.5, direction.y); + vec3 finalColor = mix(skyColor.rgb, fogColor, fogFactor); + gl_FragColor = vec4(finalColor, 1.0); + } else { + gl_FragColor = skyColor; + } } `, side: BackSide, depthWrite: false, }); - }, [skyBox, fogColor, hasFog]); - - // Update uniforms when fog parameters change - useEffect(() => { - if (materialRef.current && hasFog && shaderMaterial) { - materialRef.current.uniforms.skybox.value = skyBox; - materialRef.current.uniforms.fogColor.value = fogColor!; - } - }, [skyBox, fogColor, hasFog, shaderMaterial]); - - const { scene } = useThree(); - - // Rotate background to match the X/Z swap applied in the fog shader path - useEffect(() => { - scene.backgroundRotation = new Euler(0, Math.PI / 2, 0); - }, [scene]); - - // If fog is disabled, just use the skybox as background - if (!hasFog) { - return ; - } + }, [skyBox, fogColor]); return ( - + ); } @@ -178,21 +165,10 @@ export function Sky({ object }: { object: TorqueObject }) { // Skybox textures const materialList = getProperty(object, "materialList"); - const skySolidColor = useMemo(() => { - const colorString = getProperty(object, "SkySolidColor"); - if (colorString) { - // `colorString` might specify an alpha value, but three.js doesn't - // support opacity on fog or scene backgrounds, so ignore it. - // Note: This is a space-separated string, so we split and parse each component. - const [r, g, b] = colorString - .split(" ") - .map((s: string) => parseFloat(s)); - return [ - new Color().setRGB(r, g, b), - new Color().setRGB(r, g, b).convertSRGBToLinear(), - ]; - } - }, [object]); + const skySolidColor = useMemo( + () => parseColorString(getProperty(object, "SkySolidColor")), + [object], + ); const useSkyTextures = getInt(object, "useSkyTextures") ?? 1; @@ -213,21 +189,10 @@ export function Sky({ object }: { object: TorqueObject }) { ? highVisibleDistance : visibleDistanceBase; - const fogColor = useMemo(() => { - const colorString = getProperty(object, "fogColor"); - if (colorString) { - // `colorString` might specify an alpha value, but three.js doesn't - // support opacity on fog or scene backgrounds, so ignore it. - // Note: This is a space-separated string, so we split and parse each component. - const [r, g, b] = colorString - .split(" ") - .map((s: string) => parseFloat(s)); - return [ - new Color().setRGB(r, g, b), - new Color().setRGB(r, g, b).convertSRGBToLinear(), - ]; - } - }, [object]); + const fogColor = useMemo( + () => parseColorString(getProperty(object, "fogColor")), + [object], + ); const skyColor = skySolidColor || fogColor; @@ -245,8 +210,6 @@ export function Sky({ object }: { object: TorqueObject }) { ) : (