mirror of
https://github.com/exogen/t2-mapper.git
synced 2026-01-19 12:14:47 +00:00
add cloud layers (#13)
This commit is contained in:
parent
0839c99a9f
commit
10984c3c0f
550
src/components/CloudLayers.tsx
Normal file
550
src/components/CloudLayers.tsx
Normal file
|
|
@ -0,0 +1,550 @@
|
|||
import { Suspense, useRef, useMemo, useEffect } from "react";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useFrame } from "@react-three/fiber";
|
||||
import { useTexture } from "@react-three/drei";
|
||||
import {
|
||||
BufferGeometry,
|
||||
Float32BufferAttribute,
|
||||
ShaderMaterial,
|
||||
Vector2,
|
||||
DoubleSide,
|
||||
Texture,
|
||||
RepeatWrapping,
|
||||
LinearFilter,
|
||||
SRGBColorSpace,
|
||||
Group,
|
||||
} from "three";
|
||||
import { loadDetailMapList, textureToUrl } from "../loaders";
|
||||
import type { TorqueObject } from "../torqueScript";
|
||||
import { getFloat, getProperty } from "../mission";
|
||||
import { useDebug } from "./SettingsProvider";
|
||||
|
||||
const GRID_SIZE = 5;
|
||||
const VERTEX_COUNT = GRID_SIZE * GRID_SIZE;
|
||||
|
||||
/**
|
||||
* Cloud edge height as percentage of radius.
|
||||
* From Tribes 2 sky.cc line 160: setHeights(cloudHeightPer[i], cloudHeightPer[i]-0.05f, 0.05f)
|
||||
* Edge height is always 0.05, while center/inner heights vary per layer.
|
||||
*/
|
||||
const EDGE_HEIGHT = 0.05;
|
||||
|
||||
/**
|
||||
* Height values for each vertex in the 5x5 grid.
|
||||
* Matches the zValue array from Tribes 2 Cloud::setPoints()
|
||||
*/
|
||||
function getHeightValues(
|
||||
centerHeight: number,
|
||||
innerHeight: number,
|
||||
edgeHeight: number,
|
||||
): number[] {
|
||||
const c = centerHeight;
|
||||
const i = innerHeight;
|
||||
const e = edgeHeight;
|
||||
|
||||
// prettier-ignore
|
||||
return [
|
||||
e, e, e, e, e, // Row 0 (top edge)
|
||||
e, i, i, i, e, // Row 1
|
||||
e, i, c, i, e, // Row 2 (center)
|
||||
e, i, i, i, e, // Row 3
|
||||
e, e, e, e, e, // Row 4 (bottom edge)
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate per-vertex alpha values.
|
||||
* From Tribes 2 Cloud::calcAlpha() - fades based on distance from center.
|
||||
* Uses the exact algorithm from TorqueSDK-1.2/engine/terrain/sky.cc
|
||||
*/
|
||||
function calculateAlphaValues(
|
||||
positions: Float32Array,
|
||||
radius: number,
|
||||
): Float32Array {
|
||||
const alphas = new Float32Array(VERTEX_COUNT);
|
||||
|
||||
for (let i = 0; i < VERTEX_COUNT; i++) {
|
||||
const x = positions[i * 3];
|
||||
const z = positions[i * 3 + 2];
|
||||
// Distance from vertical axis (X/Z plane in Three.js, ignoring Y height)
|
||||
// Matches: (mPoints[i] - Point3F(0, 0, mPoints[i].z)).len()
|
||||
const dist = Math.sqrt(x * x + z * z);
|
||||
|
||||
// From Tribes 2: alpha = 1.3 - dist/radius
|
||||
let alpha = 1.3 - dist / radius;
|
||||
|
||||
// Clamp and threshold (exact values from Tribes 2)
|
||||
if (alpha < 0.4) {
|
||||
alpha = 0.0;
|
||||
} else if (alpha > 0.8) {
|
||||
alpha = 1.0;
|
||||
}
|
||||
|
||||
alphas[i] = alpha;
|
||||
}
|
||||
|
||||
return alphas;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the cloud dome geometry as a 5x5 grid of vertices.
|
||||
* Matches Tribes 2 Cloud::setPoints()
|
||||
*/
|
||||
function createCloudGeometry(
|
||||
radius: number,
|
||||
centerHeight: number,
|
||||
innerHeight: number,
|
||||
edgeHeight: number,
|
||||
): BufferGeometry {
|
||||
const geometry = new BufferGeometry();
|
||||
|
||||
const positions = new Float32Array(VERTEX_COUNT * 3);
|
||||
const uvs = new Float32Array(VERTEX_COUNT * 2);
|
||||
const heightValues = getHeightValues(centerHeight, innerHeight, edgeHeight);
|
||||
|
||||
// Grid spacing
|
||||
const step = (radius * 2) / (GRID_SIZE - 1);
|
||||
|
||||
for (let row = 0; row < GRID_SIZE; row++) {
|
||||
for (let col = 0; col < GRID_SIZE; col++) {
|
||||
const idx = row * GRID_SIZE + col;
|
||||
|
||||
// Position: from -radius to +radius in X/Z plane (Three.js Y-up)
|
||||
const x = -radius + col * step;
|
||||
const z = radius - row * step; // Z goes from +radius to -radius
|
||||
const y = radius * heightValues[idx]; // Y is up in Three.js
|
||||
|
||||
positions[idx * 3] = x;
|
||||
positions[idx * 3 + 1] = y;
|
||||
positions[idx * 3 + 2] = z;
|
||||
|
||||
// UV coordinates for texture (will be offset for scrolling)
|
||||
uvs[idx * 2] = col / (GRID_SIZE - 1);
|
||||
uvs[idx * 2 + 1] = row / (GRID_SIZE - 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Adjust corner vertices to smooth the dome (from Tribes 2 Cloud::setPoints)
|
||||
// This makes the corners lie on a plane with their neighbors
|
||||
adjustCorners(positions);
|
||||
|
||||
// Calculate alpha values based on distance from center
|
||||
const alphas = calculateAlphaValues(positions, radius);
|
||||
|
||||
// Create indices for the 4x4 grid of quads (each as 2 triangles)
|
||||
const indices: number[] = [];
|
||||
for (let row = 0; row < GRID_SIZE - 1; row++) {
|
||||
for (let col = 0; col < GRID_SIZE - 1; col++) {
|
||||
const topLeft = row * GRID_SIZE + col;
|
||||
const topRight = topLeft + 1;
|
||||
const bottomLeft = topLeft + GRID_SIZE;
|
||||
const bottomRight = bottomLeft + 1;
|
||||
|
||||
// Two triangles per quad
|
||||
indices.push(topLeft, bottomLeft, bottomRight);
|
||||
indices.push(topLeft, bottomRight, topRight);
|
||||
}
|
||||
}
|
||||
|
||||
geometry.setIndex(indices);
|
||||
geometry.setAttribute("position", new Float32BufferAttribute(positions, 3));
|
||||
geometry.setAttribute("uv", new Float32BufferAttribute(uvs, 2));
|
||||
geometry.setAttribute("alpha", new Float32BufferAttribute(alphas, 1));
|
||||
|
||||
// Compute bounding sphere for proper frustum culling
|
||||
geometry.computeBoundingSphere();
|
||||
|
||||
return geometry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjust corner vertices to create a smoother dome.
|
||||
* From Tribes 2 Cloud::setPoints() corner adjustment logic.
|
||||
* Note: In Three.js, Y is up, so we use x/z for horizontal and y for height.
|
||||
*/
|
||||
function adjustCorners(positions: Float32Array): void {
|
||||
// Helper to get/set position (x, y, z where y is up in Three.js)
|
||||
const getPos = (idx: number) => ({
|
||||
x: positions[idx * 3],
|
||||
y: positions[idx * 3 + 1], // height (up)
|
||||
z: positions[idx * 3 + 2],
|
||||
});
|
||||
const setPos = (idx: number, x: number, y: number, z: number) => {
|
||||
positions[idx * 3] = x;
|
||||
positions[idx * 3 + 1] = y;
|
||||
positions[idx * 3 + 2] = z;
|
||||
};
|
||||
|
||||
// Corner indices
|
||||
const TL = 0;
|
||||
const TR = 4;
|
||||
const BL = 20;
|
||||
const BR = 24;
|
||||
// Adjacent indices for corner adjustment
|
||||
const p1 = getPos(1);
|
||||
const p3 = getPos(3);
|
||||
const p5 = getPos(5);
|
||||
const p6 = getPos(6);
|
||||
const p8 = getPos(8);
|
||||
const p9 = getPos(9);
|
||||
const p15 = getPos(15);
|
||||
const p16 = getPos(16);
|
||||
const p18 = getPos(18);
|
||||
const p19 = getPos(19);
|
||||
const p21 = getPos(21);
|
||||
const p23 = getPos(23);
|
||||
|
||||
// Top-left corner (index 0)
|
||||
// vec = (p5 + (p1 - p5) * 0.5) - p6; p0 = p6 + vec * 2
|
||||
let midX = p5.x + (p1.x - p5.x) * 0.5;
|
||||
let midY = p5.y + (p1.y - p5.y) * 0.5;
|
||||
let midZ = p5.z + (p1.z - p5.z) * 0.5;
|
||||
setPos(
|
||||
TL,
|
||||
p6.x + (midX - p6.x) * 2,
|
||||
p6.y + (midY - p6.y) * 2,
|
||||
p6.z + (midZ - p6.z) * 2,
|
||||
);
|
||||
|
||||
// Top-right corner (index 4)
|
||||
// vec = (p9 + (p3 - p9) * 0.5) - p8; p4 = p8 + vec * 2
|
||||
midX = p9.x + (p3.x - p9.x) * 0.5;
|
||||
midY = p9.y + (p3.y - p9.y) * 0.5;
|
||||
midZ = p9.z + (p3.z - p9.z) * 0.5;
|
||||
setPos(
|
||||
TR,
|
||||
p8.x + (midX - p8.x) * 2,
|
||||
p8.y + (midY - p8.y) * 2,
|
||||
p8.z + (midZ - p8.z) * 2,
|
||||
);
|
||||
|
||||
// Bottom-left corner (index 20)
|
||||
// vec = (p21 + (p15 - p21) * 0.5) - p16; p20 = p16 + vec * 2
|
||||
midX = p21.x + (p15.x - p21.x) * 0.5;
|
||||
midY = p21.y + (p15.y - p21.y) * 0.5;
|
||||
midZ = p21.z + (p15.z - p21.z) * 0.5;
|
||||
setPos(
|
||||
BL,
|
||||
p16.x + (midX - p16.x) * 2,
|
||||
p16.y + (midY - p16.y) * 2,
|
||||
p16.z + (midZ - p16.z) * 2,
|
||||
);
|
||||
|
||||
// Bottom-right corner (index 24)
|
||||
// vec = (p23 + (p19 - p23) * 0.5) - p18; p24 = p18 + vec * 2
|
||||
midX = p23.x + (p19.x - p23.x) * 0.5;
|
||||
midY = p23.y + (p19.y - p23.y) * 0.5;
|
||||
midZ = p23.z + (p19.z - p23.z) * 0.5;
|
||||
setPos(
|
||||
BR,
|
||||
p18.x + (midX - p18.x) * 2,
|
||||
p18.y + (midY - p18.y) * 2,
|
||||
p18.z + (midZ - p18.z) * 2,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup cloud texture with proper wrapping and filtering.
|
||||
*/
|
||||
function setupCloudTexture(texture: Texture): Texture {
|
||||
texture.wrapS = RepeatWrapping;
|
||||
texture.wrapT = RepeatWrapping;
|
||||
texture.minFilter = LinearFilter;
|
||||
texture.magFilter = LinearFilter;
|
||||
texture.colorSpace = SRGBColorSpace;
|
||||
texture.needsUpdate = true;
|
||||
return texture;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vertex shader for cloud layer.
|
||||
* Passes through position, UV (with offset for scrolling), and alpha.
|
||||
* Uses xyww trick to render at far plane like skybox.
|
||||
*/
|
||||
const cloudVertexShader = `
|
||||
attribute float alpha;
|
||||
|
||||
uniform vec2 uvOffset;
|
||||
|
||||
varying vec2 vUv;
|
||||
varying float vAlpha;
|
||||
|
||||
void main() {
|
||||
// Apply UV offset for scrolling
|
||||
vUv = uv + uvOffset;
|
||||
vAlpha = alpha;
|
||||
|
||||
vec4 pos = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
||||
// Set depth to far plane so clouds are always visible and behind other geometry
|
||||
gl_Position = pos.xyww;
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* Fragment shader for cloud layer.
|
||||
* Samples texture and applies per-vertex alpha for edge fading.
|
||||
* Cloud textures in Tribes 2 use the texture's brightness for transparency.
|
||||
*/
|
||||
const cloudFragmentShader = `
|
||||
uniform sampler2D cloudTexture;
|
||||
uniform float debugMode;
|
||||
uniform int layerIndex;
|
||||
|
||||
varying vec2 vUv;
|
||||
varying float vAlpha;
|
||||
|
||||
void main() {
|
||||
vec4 texColor = texture2D(cloudTexture, vUv);
|
||||
|
||||
// Debug mode: show layer-colored clouds (red, green, blue for layers 0, 1, 2)
|
||||
if (debugMode > 0.5) {
|
||||
vec3 debugColor;
|
||||
if (layerIndex == 0) {
|
||||
debugColor = vec3(1.0, 0.3, 0.3); // Red
|
||||
} else if (layerIndex == 1) {
|
||||
debugColor = vec3(0.3, 1.0, 0.3); // Green
|
||||
} else {
|
||||
debugColor = vec3(0.3, 0.3, 1.0); // Blue
|
||||
}
|
||||
// Use same alpha calculation as normal mode
|
||||
gl_FragColor = vec4(debugColor, texColor.a * vAlpha);
|
||||
return;
|
||||
}
|
||||
|
||||
// Tribes 2 uses GL_MODULATE: final = texture × vertex color
|
||||
// Vertex color is white with varying alpha, so:
|
||||
// Final RGB = Texture RGB × 1.0 = Texture RGB
|
||||
// Final Alpha = Texture Alpha × Vertex Alpha
|
||||
float finalAlpha = texColor.a * vAlpha;
|
||||
|
||||
// Output clouds with texture color and combined alpha
|
||||
gl_FragColor = vec4(texColor.rgb, finalAlpha);
|
||||
}
|
||||
`;
|
||||
|
||||
interface CloudLayerProps {
|
||||
textureUrl: string;
|
||||
radius: number;
|
||||
heightPercent: number;
|
||||
speed: number;
|
||||
windDirection: Vector2;
|
||||
layerIndex: number;
|
||||
debugMode: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Single cloud layer component.
|
||||
*/
|
||||
function CloudLayer({
|
||||
textureUrl,
|
||||
radius,
|
||||
heightPercent,
|
||||
speed,
|
||||
windDirection,
|
||||
layerIndex,
|
||||
debugMode,
|
||||
}: CloudLayerProps) {
|
||||
const materialRef = useRef<ShaderMaterial>(null!);
|
||||
const offsetRef = useRef(new Vector2(0, 0));
|
||||
|
||||
// Load cloud texture
|
||||
const texture = useTexture(textureUrl, setupCloudTexture);
|
||||
|
||||
// Create geometry with height based on layer
|
||||
// From Tribes 2 sky.cc line 160: setHeights(cloudHeightPer[i], cloudHeightPer[i]-0.05f, 0.05f)
|
||||
const geometry = useMemo(() => {
|
||||
const centerHeight = heightPercent;
|
||||
const innerHeight = heightPercent - 0.05;
|
||||
const edgeHeight = EDGE_HEIGHT;
|
||||
return createCloudGeometry(radius, centerHeight, innerHeight, edgeHeight);
|
||||
}, [radius, heightPercent]);
|
||||
|
||||
// Create shader material
|
||||
const material = useMemo(() => {
|
||||
return new ShaderMaterial({
|
||||
uniforms: {
|
||||
cloudTexture: { value: texture },
|
||||
uvOffset: { value: new Vector2(0, 0) },
|
||||
debugMode: { value: debugMode ? 1 : 0 },
|
||||
layerIndex: { value: layerIndex },
|
||||
},
|
||||
vertexShader: cloudVertexShader,
|
||||
fragmentShader: cloudFragmentShader,
|
||||
transparent: true,
|
||||
depthWrite: false,
|
||||
side: DoubleSide,
|
||||
});
|
||||
}, [texture, debugMode, layerIndex]);
|
||||
|
||||
// Animate UV offset for cloud scrolling
|
||||
// From Tribes 2: mOffset = (currentTime - mLastTime) / 32.0 (time in ms)
|
||||
// delta is in seconds, so: delta * 1000 / 32 = delta * 31.25
|
||||
useFrame((_, delta) => {
|
||||
if (!materialRef.current) return;
|
||||
|
||||
// Match Tribes 2 timing: deltaTime(ms) / 32
|
||||
const mOffset = (delta * 1000) / 32;
|
||||
|
||||
offsetRef.current.x += windDirection.x * speed * mOffset;
|
||||
offsetRef.current.y += windDirection.y * speed * mOffset;
|
||||
|
||||
// Wrap to [0,1] range
|
||||
offsetRef.current.x = offsetRef.current.x - Math.floor(offsetRef.current.x);
|
||||
offsetRef.current.y = offsetRef.current.y - Math.floor(offsetRef.current.y);
|
||||
|
||||
materialRef.current.uniforms.uvOffset.value.copy(offsetRef.current);
|
||||
});
|
||||
|
||||
// Cleanup
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
geometry.dispose();
|
||||
material.dispose();
|
||||
};
|
||||
}, [geometry, material]);
|
||||
|
||||
return (
|
||||
<mesh geometry={geometry} frustumCulled={false} renderOrder={10}>
|
||||
<primitive ref={materialRef} object={material} attach="material" />
|
||||
</mesh>
|
||||
);
|
||||
}
|
||||
|
||||
export interface CloudLayerConfig {
|
||||
texture: string;
|
||||
height: number;
|
||||
speed: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* DML file indices:
|
||||
* 0-5: Skybox faces
|
||||
* 6: Environment map
|
||||
* 7+: Cloud layer textures
|
||||
*/
|
||||
const CLOUD_TEXTURE_OFFSET = 7;
|
||||
|
||||
/**
|
||||
* Hook to load a DML file.
|
||||
*/
|
||||
function useDetailMapList(name: string | undefined) {
|
||||
return useQuery({
|
||||
queryKey: ["detailMapList", name],
|
||||
queryFn: () => loadDetailMapList(name!),
|
||||
enabled: !!name,
|
||||
});
|
||||
}
|
||||
|
||||
export interface CloudLayersProps {
|
||||
object: TorqueObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* CloudLayers component renders multiple cloud layers as domed meshes.
|
||||
* Matches the Tribes 2 cloud rendering system.
|
||||
*
|
||||
* Reads from TorqueObject:
|
||||
* - materialList: DML file containing cloud textures at indices 7+
|
||||
* - cloudSpeed1/2/3: Speed for each cloud layer
|
||||
* - cloudHeightPer0/1/2: Height percentage for each layer
|
||||
* - windVelocity: Wind direction for cloud movement
|
||||
*/
|
||||
export function CloudLayers({ object }: CloudLayersProps) {
|
||||
const { debugMode } = useDebug();
|
||||
const materialList = getProperty(object, "materialList");
|
||||
const { data: detailMapList } = useDetailMapList(materialList);
|
||||
|
||||
// From Tribes 2 sky.cc line 1170: mRadius = visibleDistance * 0.95
|
||||
const visibleDistance = getFloat(object, "visibleDistance") ?? 500;
|
||||
const radius = visibleDistance * 0.95;
|
||||
|
||||
// Extract cloud speeds from object (cloudSpeed1/2/3 are scalar values)
|
||||
const cloudSpeeds = useMemo(
|
||||
() => [
|
||||
getFloat(object, "cloudSpeed1") ?? 0.0001,
|
||||
getFloat(object, "cloudSpeed2") ?? 0.0002,
|
||||
getFloat(object, "cloudSpeed3") ?? 0.0003,
|
||||
],
|
||||
[object],
|
||||
);
|
||||
|
||||
// Extract cloud heights from object
|
||||
// Default heights match typical Tribes 2 values (e.g., 0.35, 0.25, 0.2)
|
||||
const cloudHeights = useMemo(() => {
|
||||
const defaults = [0.35, 0.25, 0.2];
|
||||
const heights: number[] = [];
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const height = getFloat(object, `cloudHeightPer${i}`) ?? defaults[i];
|
||||
heights.push(height);
|
||||
}
|
||||
return heights;
|
||||
}, [object]);
|
||||
|
||||
// Wind direction from windVelocity
|
||||
const windDirection = useMemo(() => {
|
||||
const windVelocity = getProperty(object, "windVelocity");
|
||||
if (windVelocity) {
|
||||
const [x, y] = windVelocity.split(" ").map((s: string) => parseFloat(s));
|
||||
if (x !== 0 || y !== 0) {
|
||||
return new Vector2(x, y).normalize();
|
||||
}
|
||||
}
|
||||
return new Vector2(1, 0);
|
||||
}, [object]);
|
||||
|
||||
// Extract cloud layer configurations from DML (indices 7+)
|
||||
const layers = useMemo<CloudLayerConfig[]>(() => {
|
||||
if (!detailMapList) return [];
|
||||
|
||||
const result: CloudLayerConfig[] = [];
|
||||
for (let i = CLOUD_TEXTURE_OFFSET; i < detailMapList.length; i++) {
|
||||
const texture = detailMapList[i];
|
||||
if (texture) {
|
||||
const layerIndex = i - CLOUD_TEXTURE_OFFSET;
|
||||
result.push({
|
||||
texture,
|
||||
height: cloudHeights[layerIndex] ?? 0,
|
||||
speed: cloudSpeeds[layerIndex] ?? 0.0001 * (layerIndex + 1),
|
||||
});
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}, [detailMapList, cloudSpeeds, cloudHeights]);
|
||||
|
||||
// Reference for the group to follow camera
|
||||
const groupRef = useRef<Group>(null!);
|
||||
|
||||
// Make clouds follow camera position (they should appear infinitely far away)
|
||||
// From Tribes 2 sky.cc line 633-634: glTranslatef(camPos.x, camPos.y, camPos.z)
|
||||
// Clouds are translated to camera position in all 3 dimensions
|
||||
useFrame(({ camera }) => {
|
||||
if (groupRef.current) {
|
||||
groupRef.current.position.copy(camera.position);
|
||||
}
|
||||
});
|
||||
|
||||
if (!layers || layers.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<group ref={groupRef}>
|
||||
{layers.map((layer, i) => {
|
||||
const url = textureToUrl(layer.texture);
|
||||
return (
|
||||
<Suspense key={i} fallback={null}>
|
||||
<CloudLayer
|
||||
textureUrl={url}
|
||||
radius={radius}
|
||||
heightPercent={layer.height}
|
||||
speed={layer.speed}
|
||||
windDirection={windDirection}
|
||||
layerIndex={i}
|
||||
debugMode={debugMode}
|
||||
/>
|
||||
</Suspense>
|
||||
);
|
||||
})}
|
||||
</group>
|
||||
);
|
||||
}
|
||||
|
|
@ -3,10 +3,11 @@ import { useQuery } from "@tanstack/react-query";
|
|||
import { useCubeTexture } from "@react-three/drei";
|
||||
import { Color, ShaderMaterial, BackSide, Euler } from "three";
|
||||
import type { TorqueObject } from "../torqueScript";
|
||||
import { getFloat, getProperty } from "../mission";
|
||||
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`;
|
||||
|
||||
|
|
@ -162,6 +163,8 @@ export function Sky({ object }: { object: TorqueObject }) {
|
|||
}
|
||||
}, [object]);
|
||||
|
||||
const useSkyTextures = getInt(object, "useSkyTextures") ?? 1;
|
||||
|
||||
// Fog parameters.
|
||||
// TODO: There can be multiple fog volumes/layers. Render simple fog for now.
|
||||
const fogDistance = getFloat(object, "fogDistance");
|
||||
|
|
@ -190,20 +193,24 @@ export function Sky({ object }: { object: TorqueObject }) {
|
|||
|
||||
return (
|
||||
<>
|
||||
{materialList ? (
|
||||
// If there's a skybox, its textures will need to load. Render just the
|
||||
// fog color as the background in the meantime.
|
||||
{materialList && useSkyTextures ? (
|
||||
// Load the DML for skybox textures
|
||||
<Suspense fallback={backgroundColor}>
|
||||
<SkyBox
|
||||
materialList={materialList}
|
||||
fogColor={fogEnabled ? fogColor[1] : undefined}
|
||||
fogColor={fogEnabled ? fogColor?.[1] : undefined}
|
||||
fogDistance={fogEnabled ? fogDistance : undefined}
|
||||
/>
|
||||
</Suspense>
|
||||
) : (
|
||||
// If there's no skybox, just render the fog color as the background.
|
||||
// If there's no material list or skybox textures are disabled,
|
||||
// render solid background
|
||||
backgroundColor
|
||||
)}
|
||||
{/* Cloud layers render independently of skybox textures */}
|
||||
<Suspense>
|
||||
<CloudLayers object={object} />
|
||||
</Suspense>
|
||||
{fogEnabled && fogDistance && fogColor ? (
|
||||
<fog
|
||||
attach="fog"
|
||||
|
|
|
|||
Loading…
Reference in a new issue