Compare commits
No commits in common. "638561d86d1e53730bf0eada19af19f414fa22f0" and "3d6a57cd0b9617035f4c70790edb0666c2db5713" have entirely different histories.
638561d86d
...
3d6a57cd0b
|
|
@ -4,7 +4,6 @@
|
||||||
# OS files
|
# OS files
|
||||||
.DS_Store
|
.DS_Store
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
.gemini-clipboard/
|
|
||||||
|
|
||||||
# Environments
|
# Environments
|
||||||
.env*
|
.env*
|
||||||
|
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 69 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
|
|
@ -1,5 +1,5 @@
|
||||||
import { useRef, useMemo, useState, useEffect, Suspense } from 'react';
|
import { useRef, useMemo, useState, useEffect, Suspense } from 'react';
|
||||||
import { Mesh, Group, DoubleSide } from 'three';
|
import { Mesh } from 'three';
|
||||||
import * as THREE from 'three';
|
import * as THREE from 'three';
|
||||||
import { useGLTF, useTexture } from '@react-three/drei';
|
import { useGLTF, useTexture } from '@react-three/drei';
|
||||||
import { useFrame } from '@react-three/fiber';
|
import { useFrame } from '@react-three/fiber';
|
||||||
|
|
@ -12,75 +12,10 @@ interface BodyViewerProps {
|
||||||
disableGlow?: boolean; // 禁用光晕效果(用于详情视图)
|
disableGlow?: boolean; // 禁用光晕效果(用于详情视图)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Planetary Rings component - handles texture-based rings
|
// Reusable component to render just the 3D model/mesh of a celestial body
|
||||||
function PlanetaryRings({ texturePath, planetRadius }: { texturePath?: string | null, planetRadius: number }) {
|
|
||||||
const texture = texturePath ? useTexture(texturePath) : null;
|
|
||||||
const meshRef = useRef<Mesh>(null);
|
|
||||||
|
|
||||||
// Dynamic ring dimensions based on planet size
|
|
||||||
// Standard Saturn proportions: Inner ~1.1 R, Outer ~2.3 R
|
|
||||||
const innerRadius = planetRadius * 1.05;
|
|
||||||
const outerRadius = planetRadius * 2.2;
|
|
||||||
|
|
||||||
// Custom geometry with Polar UV mapping for strip textures
|
|
||||||
const geometry = useMemo(() => {
|
|
||||||
if (!texturePath) return null;
|
|
||||||
|
|
||||||
const geo = new THREE.RingGeometry(innerRadius, outerRadius, 64);
|
|
||||||
const pos = geo.attributes.position;
|
|
||||||
const uv = geo.attributes.uv;
|
|
||||||
|
|
||||||
// Manually remap UVs to be polar:
|
|
||||||
// We map normalized radius to BOTH U and V coordinates.
|
|
||||||
// This allows the texture to work whether it's a horizontal strip OR a vertical strip.
|
|
||||||
// - If horizontal strip: U varies with radius (correct), V varies (doesn't matter as strip is uniform vertically)
|
|
||||||
// - If vertical strip: V varies with radius (correct), U varies (doesn't matter as strip is uniform horizontally)
|
|
||||||
for (let i = 0; i < pos.count; i++) {
|
|
||||||
const x = pos.getX(i);
|
|
||||||
const y = pos.getY(i);
|
|
||||||
|
|
||||||
// Calculate radius from center
|
|
||||||
const r = Math.sqrt(x * x + y * y);
|
|
||||||
|
|
||||||
// Normalize radius: 0 at inner, 1 at outer
|
|
||||||
// Clamp to ensure floating point errors don't break texture wrapping
|
|
||||||
const normalizedR = Math.max(0, Math.min(1, (r - innerRadius) / (outerRadius - innerRadius)));
|
|
||||||
|
|
||||||
// Map to UV (same value for both axes to support H/V strips)
|
|
||||||
uv.setXY(i, normalizedR, normalizedR);
|
|
||||||
}
|
|
||||||
|
|
||||||
geo.attributes.uv.needsUpdate = true;
|
|
||||||
return geo;
|
|
||||||
}, [texturePath, innerRadius, outerRadius]);
|
|
||||||
|
|
||||||
if (texture && geometry) {
|
|
||||||
return (
|
|
||||||
<mesh
|
|
||||||
ref={meshRef}
|
|
||||||
rotation={[-Math.PI / 2, 0, 0]}
|
|
||||||
geometry={geometry}
|
|
||||||
receiveShadow
|
|
||||||
castShadow
|
|
||||||
>
|
|
||||||
<meshStandardMaterial
|
|
||||||
map={texture}
|
|
||||||
transparent
|
|
||||||
opacity={0.9}
|
|
||||||
side={DoubleSide}
|
|
||||||
color="#ffffff"
|
|
||||||
roughness={0.8}
|
|
||||||
/>
|
|
||||||
</mesh>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
export function BodyViewer({ body, disableGlow = false }: BodyViewerProps) {
|
export function BodyViewer({ body, disableGlow = false }: BodyViewerProps) {
|
||||||
const meshRef = useRef<Mesh>(null);
|
const meshRef = useRef<Mesh>(null);
|
||||||
const [texturePath, setTexturePath] = useState<string | null | undefined>(undefined);
|
const [texturePath, setTexturePath] = useState<string | null | undefined>(undefined);
|
||||||
const [ringTexturePath, setRingTexturePath] = useState<string | null | undefined>(undefined);
|
|
||||||
const [modelPath, setModelPath] = useState<string | null | undefined>(undefined);
|
const [modelPath, setModelPath] = useState<string | null | undefined>(undefined);
|
||||||
const [modelScale, setModelScale] = useState<number>(1.0);
|
const [modelScale, setModelScale] = useState<number>(1.0);
|
||||||
const [loadError, setLoadError] = useState<boolean>(false);
|
const [loadError, setLoadError] = useState<boolean>(false);
|
||||||
|
|
@ -127,62 +62,31 @@ export function BodyViewer({ body, disableGlow = false }: BodyViewerProps) {
|
||||||
setLoadError(false);
|
setLoadError(false);
|
||||||
setModelPath(undefined);
|
setModelPath(undefined);
|
||||||
setTexturePath(undefined);
|
setTexturePath(undefined);
|
||||||
setRingTexturePath(undefined);
|
|
||||||
|
|
||||||
const loadResources = async () => {
|
const loadResources = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await fetchBodyResources(body.id, body.type === 'probe' ? 'model' : 'texture');
|
const response = await fetchBodyResources(body.id, body.type === 'probe' ? 'model' : 'texture');
|
||||||
if (response.resources.length > 0) {
|
if (response.resources.length > 0) {
|
||||||
|
const mainResource = response.resources[0];
|
||||||
if (body.type === 'probe') {
|
const fullPath = `/upload/${mainResource.file_path}`;
|
||||||
const mainResource = response.resources[0];
|
|
||||||
const fullPath = `/upload/${mainResource.file_path}`;
|
|
||||||
useGLTF.preload(fullPath); // Preload GLTF
|
|
||||||
setModelPath(fullPath);
|
|
||||||
setModelScale(mainResource.extra_data?.scale || 1.0);
|
|
||||||
} else {
|
|
||||||
// Find main texture
|
|
||||||
const mainTexture = response.resources.find(
|
|
||||||
(r) => !r.file_path.includes('atmosphere') &&
|
|
||||||
!r.file_path.includes('night') &&
|
|
||||||
!r.file_path.includes('_ring')
|
|
||||||
);
|
|
||||||
|
|
||||||
// Find ring texture
|
|
||||||
const bodyNameLower = body.name.toLowerCase();
|
|
||||||
const ringTexture = response.resources.find(
|
|
||||||
(r) => r.file_path.toLowerCase().includes(`${bodyNameLower}_ring`) ||
|
|
||||||
(r.file_path.toLowerCase().includes('ring') && r.file_path.toLowerCase().includes(bodyNameLower))
|
|
||||||
);
|
|
||||||
|
|
||||||
if (mainTexture) {
|
if (body.type === 'probe') {
|
||||||
setTexturePath(`/upload/${mainTexture.file_path}`);
|
useGLTF.preload(fullPath); // Preload GLTF
|
||||||
} else {
|
setModelPath(fullPath);
|
||||||
setTexturePath(null);
|
setModelScale(mainResource.extra_data?.scale || 1.0);
|
||||||
}
|
} else {
|
||||||
|
setTexturePath(fullPath);
|
||||||
if (ringTexture) {
|
|
||||||
setRingTexturePath(`/upload/${ringTexture.file_path}`);
|
|
||||||
} else {
|
|
||||||
setRingTexturePath(null);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// No resources found
|
// No resources found
|
||||||
if (body.type === 'probe') setModelPath(null);
|
if (body.type === 'probe') setModelPath(null);
|
||||||
else {
|
else setTexturePath(null);
|
||||||
setTexturePath(null);
|
|
||||||
setRingTexturePath(null);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(`Failed to load resource for ${body.name}:`, err);
|
console.error(`Failed to load resource for ${body.name}:`, err);
|
||||||
setLoadError(true);
|
setLoadError(true);
|
||||||
if (body.type === 'probe') setModelPath(null);
|
if (body.type === 'probe') setModelPath(null);
|
||||||
else {
|
else setTexturePath(null);
|
||||||
setTexturePath(null);
|
|
||||||
setRingTexturePath(null);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
loadResources();
|
loadResources();
|
||||||
|
|
@ -211,7 +115,6 @@ export function BodyViewer({ body, disableGlow = false }: BodyViewerProps) {
|
||||||
emissive={appearance.emissive}
|
emissive={appearance.emissive}
|
||||||
emissiveIntensity={appearance.emissiveIntensity}
|
emissiveIntensity={appearance.emissiveIntensity}
|
||||||
texturePath={texturePath}
|
texturePath={texturePath}
|
||||||
ringTexturePath={ringTexturePath}
|
|
||||||
meshRef={meshRef}
|
meshRef={meshRef}
|
||||||
disableGlow={disableGlow}
|
disableGlow={disableGlow}
|
||||||
/>
|
/>
|
||||||
|
|
@ -276,13 +179,12 @@ function ProbeModelViewer({ modelPath, modelScale }: { modelPath: string; modelS
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sub-component for Planet models
|
// Sub-component for Planet models
|
||||||
function PlanetModelViewer({ body, size, emissive, emissiveIntensity, texturePath, ringTexturePath, meshRef, disableGlow = false }: {
|
function PlanetModelViewer({ body, size, emissive, emissiveIntensity, texturePath, meshRef, disableGlow = false }: {
|
||||||
body: CelestialBodyType;
|
body: CelestialBodyType;
|
||||||
size: number;
|
size: number;
|
||||||
emissive: string;
|
emissive: string;
|
||||||
emissiveIntensity: number;
|
emissiveIntensity: number;
|
||||||
texturePath: string | null;
|
texturePath: string | null;
|
||||||
ringTexturePath?: string | null;
|
|
||||||
meshRef: React.RefObject<Mesh>;
|
meshRef: React.RefObject<Mesh>;
|
||||||
disableGlow?: boolean;
|
disableGlow?: boolean;
|
||||||
}) {
|
}) {
|
||||||
|
|
@ -429,6 +331,39 @@ function PlanetModelViewer({ body, size, emissive, emissiveIntensity, texturePat
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Saturn Rings component - multiple rings for band effect
|
||||||
|
function SaturnRings() {
|
||||||
|
return (
|
||||||
|
<group rotation={[Math.PI / 2, 0, 0]}>
|
||||||
|
{/* Inner bright ring */}
|
||||||
|
<mesh>
|
||||||
|
<ringGeometry args={[1.4, 1.6, 32]} />
|
||||||
|
<meshBasicMaterial color="#D4B896" transparent opacity={0.7} side={THREE.DoubleSide} />
|
||||||
|
</mesh>
|
||||||
|
{/* Middle darker band */}
|
||||||
|
<mesh>
|
||||||
|
<ringGeometry args={[1.6, 1.75, 32]} />
|
||||||
|
<meshBasicMaterial color="#8B7355" transparent opacity={0.5} side={THREE.DoubleSide} />
|
||||||
|
</mesh>
|
||||||
|
{/* Outer bright ring */}
|
||||||
|
<mesh>
|
||||||
|
<ringGeometry args={[1.75, 2.0, 32]} />
|
||||||
|
<meshBasicMaterial color="#C4A582" transparent opacity={0.6} side={THREE.DoubleSide} />
|
||||||
|
</mesh>
|
||||||
|
{/* Cassini Division (gap) */}
|
||||||
|
<mesh>
|
||||||
|
<ringGeometry args={[2.0, 2.05, 32]} />
|
||||||
|
<meshBasicMaterial color="#000000" transparent opacity={0.2} side={THREE.DoubleSide} />
|
||||||
|
</mesh>
|
||||||
|
{/* A Ring (outer) */}
|
||||||
|
<mesh>
|
||||||
|
<ringGeometry args={[2.05, 2.2, 32]} />
|
||||||
|
<meshBasicMaterial color="#B89968" transparent opacity={0.5} side={THREE.DoubleSide} />
|
||||||
|
</mesh>
|
||||||
|
</group>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<group>
|
<group>
|
||||||
{/* Use irregular nucleus for comets, regular sphere for others */}
|
{/* Use irregular nucleus for comets, regular sphere for others */}
|
||||||
|
|
@ -460,10 +395,8 @@ function PlanetModelViewer({ body, size, emissive, emissiveIntensity, texturePat
|
||||||
</mesh>
|
</mesh>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Planetary Rings: Render ONLY if texture exists */}
|
{/* Saturn Rings */}
|
||||||
{ringTexturePath && (
|
{body.id === '699' && <SaturnRings />}
|
||||||
<PlanetaryRings texturePath={ringTexturePath} planetRadius={size} />
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Sun glow effect - multi-layer scattered light (仅在非禁用光晕模式下显示) */}
|
{/* Sun glow effect - multi-layer scattered light (仅在非禁用光晕模式下显示) */}
|
||||||
{body.type === 'star' && !disableGlow && (
|
{body.type === 'star' && !disableGlow && (
|
||||||
|
|
|
||||||
|
|
@ -19,70 +19,62 @@ interface CelestialBodyProps {
|
||||||
onBodySelect?: (body: CelestialBodyType) => void;
|
onBodySelect?: (body: CelestialBodyType) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Planetary Rings component - handles texture-based rings
|
// Saturn Rings component - multiple rings for band effect
|
||||||
function PlanetaryRings({ texturePath, planetRadius }: { texturePath?: string | null, planetRadius: number }) {
|
function SaturnRings() {
|
||||||
const texture = texturePath ? useTexture(texturePath) : null;
|
return (
|
||||||
const meshRef = useRef<Mesh>(null);
|
<group rotation={[Math.PI / 2, 0, 0]}>
|
||||||
|
{/* Inner bright ring */}
|
||||||
// Dynamic ring dimensions based on planet size
|
<mesh>
|
||||||
// Standard Saturn proportions: Inner ~1.1 R, Outer ~2.3 R
|
<ringGeometry args={[1.4, 1.6, 32]} />
|
||||||
const innerRadius = planetRadius * 1.05;
|
<meshBasicMaterial
|
||||||
const outerRadius = planetRadius * 2.2;
|
color="#D4B896"
|
||||||
|
|
||||||
// Custom geometry with Polar UV mapping for strip textures
|
|
||||||
const geometry = useMemo(() => {
|
|
||||||
if (!texturePath) return null;
|
|
||||||
|
|
||||||
const geo = new THREE.RingGeometry(innerRadius, outerRadius, 64);
|
|
||||||
const pos = geo.attributes.position;
|
|
||||||
const uv = geo.attributes.uv;
|
|
||||||
|
|
||||||
// Manually remap UVs to be polar:
|
|
||||||
// We map normalized radius to BOTH U and V coordinates.
|
|
||||||
// This allows the texture to work whether it's a horizontal strip OR a vertical strip.
|
|
||||||
// - If horizontal strip: U varies with radius (correct), V varies (doesn't matter as strip is uniform vertically)
|
|
||||||
// - If vertical strip: V varies with radius (correct), U varies (doesn't matter as strip is uniform horizontally)
|
|
||||||
for (let i = 0; i < pos.count; i++) {
|
|
||||||
const x = pos.getX(i);
|
|
||||||
const y = pos.getY(i);
|
|
||||||
|
|
||||||
// Calculate radius from center
|
|
||||||
const r = Math.sqrt(x * x + y * y);
|
|
||||||
|
|
||||||
// Normalize radius: 0 at inner, 1 at outer
|
|
||||||
// Clamp to ensure floating point errors don't break texture wrapping
|
|
||||||
const normalizedR = Math.max(0, Math.min(1, (r - innerRadius) / (outerRadius - innerRadius)));
|
|
||||||
|
|
||||||
// Map to UV (same value for both axes to support H/V strips)
|
|
||||||
uv.setXY(i, normalizedR, normalizedR);
|
|
||||||
}
|
|
||||||
|
|
||||||
geo.attributes.uv.needsUpdate = true;
|
|
||||||
return geo;
|
|
||||||
}, [texturePath, innerRadius, outerRadius]);
|
|
||||||
|
|
||||||
if (texture && geometry) {
|
|
||||||
return (
|
|
||||||
<mesh
|
|
||||||
ref={meshRef}
|
|
||||||
rotation={[-Math.PI / 2, 0, 0]}
|
|
||||||
geometry={geometry}
|
|
||||||
receiveShadow
|
|
||||||
castShadow
|
|
||||||
>
|
|
||||||
<meshStandardMaterial
|
|
||||||
map={texture}
|
|
||||||
transparent
|
transparent
|
||||||
opacity={0.9}
|
opacity={0.7}
|
||||||
side={DoubleSide}
|
side={DoubleSide}
|
||||||
color="#ffffff"
|
|
||||||
roughness={0.8}
|
|
||||||
/>
|
/>
|
||||||
</mesh>
|
</mesh>
|
||||||
);
|
{/* Middle darker band */}
|
||||||
}
|
<mesh>
|
||||||
|
<ringGeometry args={[1.6, 1.75, 32]} />
|
||||||
return null;
|
<meshBasicMaterial
|
||||||
|
color="#8B7355"
|
||||||
|
transparent
|
||||||
|
opacity={0.5}
|
||||||
|
side={DoubleSide}
|
||||||
|
/>
|
||||||
|
</mesh>
|
||||||
|
{/* Outer bright ring */}
|
||||||
|
<mesh>
|
||||||
|
<ringGeometry args={[1.75, 2.0, 32]} />
|
||||||
|
<meshBasicMaterial
|
||||||
|
color="#C4A582"
|
||||||
|
transparent
|
||||||
|
opacity={0.6}
|
||||||
|
side={DoubleSide}
|
||||||
|
/>
|
||||||
|
</mesh>
|
||||||
|
{/* Cassini Division (gap) */}
|
||||||
|
<mesh>
|
||||||
|
<ringGeometry args={[2.0, 2.05, 32]} />
|
||||||
|
<meshBasicMaterial
|
||||||
|
color="#000000"
|
||||||
|
transparent
|
||||||
|
opacity={0.2}
|
||||||
|
side={DoubleSide}
|
||||||
|
/>
|
||||||
|
</mesh>
|
||||||
|
{/* A Ring (outer) */}
|
||||||
|
<mesh>
|
||||||
|
<ringGeometry args={[2.05, 2.2, 32]} />
|
||||||
|
<meshBasicMaterial
|
||||||
|
color="#B89968"
|
||||||
|
transparent
|
||||||
|
opacity={0.5}
|
||||||
|
side={DoubleSide}
|
||||||
|
/>
|
||||||
|
</mesh>
|
||||||
|
</group>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Planet component with texture
|
// Planet component with texture
|
||||||
|
|
@ -98,7 +90,6 @@ function Planet({ body, size, emissive, emissiveIntensity, allBodies, isSelected
|
||||||
const meshRef = useRef<Mesh>(null);
|
const meshRef = useRef<Mesh>(null);
|
||||||
const position = body.positions[0];
|
const position = body.positions[0];
|
||||||
const [texturePath, setTexturePath] = useState<string | null | undefined>(undefined);
|
const [texturePath, setTexturePath] = useState<string | null | undefined>(undefined);
|
||||||
const [ringTexturePath, setRingTexturePath] = useState<string | null | undefined>(undefined);
|
|
||||||
|
|
||||||
// Use smart render position calculation
|
// Use smart render position calculation
|
||||||
const renderPosition = useMemo(() => {
|
const renderPosition = useMemo(() => {
|
||||||
|
|
@ -111,40 +102,23 @@ function Planet({ body, size, emissive, emissiveIntensity, allBodies, isSelected
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchBodyResources(body.id, 'texture')
|
fetchBodyResources(body.id, 'texture')
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
// 1. Find the main texture (body surface)
|
// Find the main texture (not atmosphere or night layers)
|
||||||
// Exclude atmosphere, night, and ring textures from main body texture
|
|
||||||
const mainTexture = response.resources.find(
|
const mainTexture = response.resources.find(
|
||||||
(r) => !r.file_path.includes('atmosphere') &&
|
(r) => !r.file_path.includes('atmosphere') && !r.file_path.includes('night')
|
||||||
!r.file_path.includes('night') &&
|
|
||||||
!r.file_path.includes('_ring')
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// 2. Find the ring texture
|
|
||||||
// Convention: filename contains body name + "_ring" (e.g. "saturn_ring")
|
|
||||||
const bodyNameLower = body.name.toLowerCase();
|
|
||||||
const ringTexture = response.resources.find(
|
|
||||||
(r) => r.file_path.toLowerCase().includes(`${bodyNameLower}_ring`) ||
|
|
||||||
(r.file_path.toLowerCase().includes('ring') && r.file_path.toLowerCase().includes(bodyNameLower))
|
|
||||||
);
|
|
||||||
|
|
||||||
if (mainTexture) {
|
if (mainTexture) {
|
||||||
|
// Construct path for Nginx proxy
|
||||||
|
// file_path is like "texture/2k_sun.jpg", need to add "upload/" prefix
|
||||||
setTexturePath(`/upload/${mainTexture.file_path}`);
|
setTexturePath(`/upload/${mainTexture.file_path}`);
|
||||||
} else {
|
} else {
|
||||||
setTexturePath(null);
|
setTexturePath(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ringTexture) {
|
|
||||||
setRingTexturePath(`/upload/${ringTexture.file_path}`);
|
|
||||||
} else {
|
|
||||||
setRingTexturePath(null);
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
if (import.meta.env.DEV) {
|
if (import.meta.env.DEV) {
|
||||||
console.error(`Failed to load texture for ${body.name}:`, err);
|
console.error(`Failed to load texture for ${body.name}:`, err);
|
||||||
}
|
}
|
||||||
setTexturePath(null);
|
setTexturePath(null);
|
||||||
setRingTexturePath(null);
|
|
||||||
});
|
});
|
||||||
}, [body.id, body.name]);
|
}, [body.id, body.name]);
|
||||||
|
|
||||||
|
|
@ -160,7 +134,6 @@ function Planet({ body, size, emissive, emissiveIntensity, allBodies, isSelected
|
||||||
emissiveIntensity={emissiveIntensity}
|
emissiveIntensity={emissiveIntensity}
|
||||||
scaledPos={scaledPos}
|
scaledPos={scaledPos}
|
||||||
texturePath={texturePath}
|
texturePath={texturePath}
|
||||||
ringTexturePath={ringTexturePath}
|
|
||||||
position={position}
|
position={position}
|
||||||
meshRef={meshRef}
|
meshRef={meshRef}
|
||||||
hasOffset={renderPosition.hasOffset}
|
hasOffset={renderPosition.hasOffset}
|
||||||
|
|
@ -311,14 +284,13 @@ function CometComa({ radius }: { radius: number }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Separate component to handle texture loading
|
// Separate component to handle texture loading
|
||||||
function PlanetMesh({ body, size, emissive, emissiveIntensity, scaledPos, texturePath, ringTexturePath, position, meshRef, hasOffset, allBodies, isSelected = false, onBodySelect }: {
|
function PlanetMesh({ body, size, emissive, emissiveIntensity, scaledPos, texturePath, position, meshRef, hasOffset, allBodies, isSelected = false, onBodySelect }: {
|
||||||
body: CelestialBodyType;
|
body: CelestialBodyType;
|
||||||
size: number;
|
size: number;
|
||||||
emissive: string;
|
emissive: string;
|
||||||
emissiveIntensity: number;
|
emissiveIntensity: number;
|
||||||
scaledPos: { x: number; y: number; z: number };
|
scaledPos: { x: number; y: number; z: number };
|
||||||
texturePath: string | null;
|
texturePath: string | null;
|
||||||
ringTexturePath?: string | null;
|
|
||||||
position: { x: number; y: number; z: number };
|
position: { x: number; y: number; z: number };
|
||||||
meshRef: React.RefObject<Mesh>;
|
meshRef: React.RefObject<Mesh>;
|
||||||
hasOffset: boolean;
|
hasOffset: boolean;
|
||||||
|
|
@ -404,10 +376,8 @@ function PlanetMesh({ body, size, emissive, emissiveIntensity, scaledPos, textur
|
||||||
</mesh>
|
</mesh>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Planetary Rings: Render ONLY if texture exists (Standardized) */}
|
{/* Saturn Rings */}
|
||||||
{ringTexturePath && (
|
{body.id === '699' && <SaturnRings />}
|
||||||
<PlanetaryRings texturePath={ringTexturePath} planetRadius={size} />
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Sun glow effect - multi-layer scattered light */}
|
{/* Sun glow effect - multi-layer scattered light */}
|
||||||
{body.type === 'star' && (
|
{body.type === 'star' && (
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue