大幅调整的一个版本,待测试

main
mula.liu 2025-12-04 21:43:43 +08:00
parent 088529d6c4
commit 66fceb03de
10 changed files with 219 additions and 69 deletions

View File

@ -2,7 +2,7 @@
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Cosmo - 深空探测器可视化</title>
</head>

View File

@ -0,0 +1,44 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
<!-- Dark space background -->
<rect width="100" height="100" fill="#0a0a1f"/>
<!-- Galaxy spiral -->
<defs>
<radialGradient id="galaxyGlow" cx="50%" cy="50%">
<stop offset="0%" style="stop-color:#88ccff;stop-opacity:0.8"/>
<stop offset="50%" style="stop-color:#4488aa;stop-opacity:0.4"/>
<stop offset="100%" style="stop-color:#0a0a1f;stop-opacity:0"/>
</radialGradient>
<radialGradient id="coreGlow" cx="50%" cy="50%">
<stop offset="0%" style="stop-color:#ffffff;stop-opacity:1"/>
<stop offset="30%" style="stop-color:#88ccff;stop-opacity:0.8"/>
<stop offset="100%" style="stop-color:#0a0a1f;stop-opacity:0"/>
</radialGradient>
</defs>
<!-- Outer galaxy glow -->
<circle cx="50" cy="50" r="40" fill="url(#galaxyGlow)" opacity="0.3"/>
<!-- Galaxy core -->
<ellipse cx="50" cy="50" rx="28" ry="18" fill="url(#galaxyGlow)" opacity="0.6" transform="rotate(30 50 50)"/>
<ellipse cx="50" cy="50" rx="24" ry="14" fill="url(#galaxyGlow)" opacity="0.7" transform="rotate(30 50 50)"/>
<!-- Bright center -->
<circle cx="50" cy="50" r="8" fill="url(#coreGlow)"/>
<!-- Stars -->
<circle cx="20" cy="25" r="1.5" fill="#ffffff" opacity="0.9"/>
<circle cx="75" cy="30" r="1" fill="#88ccff" opacity="0.8"/>
<circle cx="85" cy="65" r="1.2" fill="#ffffff" opacity="0.9"/>
<circle cx="15" cy="70" r="1" fill="#88ccff" opacity="0.7"/>
<circle cx="30" cy="85" r="1.5" fill="#ffffff" opacity="0.8"/>
<circle cx="70" cy="20" r="0.8" fill="#ffffff" opacity="0.9"/>
<circle cx="90" cy="45" r="1" fill="#88ccff" opacity="0.8"/>
<circle cx="12" cy="50" r="1.2" fill="#ffffff" opacity="0.7"/>
<!-- Tiny sparkles -->
<circle cx="35" cy="20" r="0.5" fill="#ffffff" opacity="0.6"/>
<circle cx="65" cy="75" r="0.5" fill="#88ccff" opacity="0.6"/>
<circle cx="25" cy="60" r="0.5" fill="#ffffff" opacity="0.5"/>
<circle cx="80" cy="80" r="0.5" fill="#88ccff" opacity="0.6"/>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -117,7 +117,7 @@ function App() {
// Filter probes and planets from all bodies
const probes = bodies.filter((b) => b.type === 'probe');
const planets = bodies.filter((b) =>
b.type === 'planet' || b.type === 'dwarf_planet' || b.type === 'satellite' || b.type === 'comet'
b.type === 'planet' || b.type === 'dwarf_planet' || b.type === 'satellite' || b.type === 'comet' || b.type === 'star'
);
const handleBodySelect = (body: CelestialBody | null) => {

View File

@ -16,15 +16,8 @@ interface BodyDetailOverlayProps {
onClose: () => void;
}
// Custom camera control for automatic rotation
function AutoRotateCamera() {
useFrame((state) => {
state.camera.position.x = Math.sin(state.clock.elapsedTime * 0.1) * 3;
state.camera.position.z = Math.cos(state.clock.elapsedTime * 0.1) * 3;
state.camera.lookAt(0, 0, 0);
});
return null;
}
// No auto-rotation - user controls the view
// Removed AutoRotateCamera to allow user to control the view angle
export function BodyDetailOverlay({ bodyId, onClose }: BodyDetailOverlayProps) {
@ -74,21 +67,21 @@ export function BodyDetailOverlay({ bodyId, onClose }: BodyDetailOverlayProps) {
{loading ? (
<div className="absolute inset-0 flex items-center justify-center text-blue-300">...</div>
) : (
<Canvas camera={{ position: [3, 2, 3], fov: 60 }}>
<ambientLight intensity={0.5} />
<pointLight position={[10, 10, 10]} intensity={1} />
<pointLight position={[-10, -10, -10]} intensity={0.5} color="#88aaff" />
<directionalLight position={[0, 0, 5]} intensity={0.5} /> {/* Frontal light */}
<directionalLight position={[0, 0, -5]} intensity={0.2} /> {/* Back light */}
<AutoRotateCamera /> {/* Auto rotate for presentation */}
<Canvas camera={{ position: [0, 0, 5], fov: 50 }}>
<ambientLight intensity={0.6} />
<pointLight position={[10, 10, 10]} intensity={1.2} />
<pointLight position={[-10, -10, -10]} intensity={0.6} color="#88aaff" />
<directionalLight position={[5, 0, 5]} intensity={0.8} />
<directionalLight position={[-5, 0, -5]} intensity={0.4} />
<OrbitControls
enableZoom={true}
enablePan={false}
enableRotate={true}
minDistance={0.5}
maxDistance={10}
minDistance={1}
maxDistance={8}
maxPolarAngle={Math.PI}
minPolarAngle={0}
/>
<BodyViewer body={bodyData} />
</Canvas>

View File

@ -20,18 +20,39 @@ export function BodyViewer({ body }: BodyViewerProps) {
const [modelScale, setModelScale] = useState<number>(1.0);
const [loadError, setLoadError] = useState<boolean>(false);
// Determine size and appearance
// Determine size and appearance - use larger sizes for detail view with a cap
const appearance = useMemo(() => {
const baseSize = getCelestialSize(body.name, body.type);
// Detail view scaling strategy:
// - Small bodies (< 0.15): scale up 3x for visibility
// - Medium bodies (0.15-0.3): scale up 2x
// - Large bodies (> 0.3): use base size with max cap of 1.2
let finalSize: number;
if (body.type === 'star') {
return { size: 0.4, emissive: '#FDB813', emissiveIntensity: 1.5 };
finalSize = 1.0; // Fixed size for stars
} else if (baseSize < 0.15) {
// Small bodies: scale up for visibility
finalSize = baseSize * 3.0;
} else if (baseSize < 0.3) {
// Medium bodies: moderate scaling
finalSize = baseSize * 2.0;
} else {
// Large bodies: minimal or no scaling with a cap
finalSize = Math.min(baseSize * 1.2, 1.2);
}
if (body.type === 'star') {
return { size: finalSize, emissive: '#FDB813', emissiveIntensity: 1.5 };
}
if (body.type === 'comet') {
return { size: getCelestialSize(body.name, body.type), emissive: '#000000', emissiveIntensity: 0 };
return { size: finalSize, emissive: '#000000', emissiveIntensity: 0 };
}
if (body.type === 'satellite') {
return { size: getCelestialSize(body.name, body.type), emissive: '#888888', emissiveIntensity: 0.4 };
return { size: finalSize, emissive: '#888888', emissiveIntensity: 0.4 };
}
return { size: getCelestialSize(body.name, body.type), emissive: '#000000', emissiveIntensity: 0 };
return { size: finalSize, emissive: '#000000', emissiveIntensity: 0 };
}, [body.name, body.type]);
// Fetch resources (texture or model)
@ -113,9 +134,9 @@ function ProbeModelViewer({ modelPath, modelScale }: { modelPath: string; modelS
const size = new THREE.Vector3();
box.getSize(size);
const maxDimension = Math.max(size.x, size.y, size.z);
const targetSize = 0.35; // Standardize view
const calculatedScale = maxDimension > 0 ? targetSize / maxDimension : 0.3;
const finalScale = Math.max(0.2, Math.min(1.0, calculatedScale));
const targetSize = 1.2; // Increased from 0.35 to 1.2 for detail view - larger probes
const calculatedScale = maxDimension > 0 ? targetSize / maxDimension : 0.8;
const finalScale = Math.max(0.5, Math.min(3.0, calculatedScale)); // Increased range for larger display
return finalScale * modelScale;
}, [scene, modelScale]);
@ -373,13 +394,57 @@ function PlanetModelViewer({ body, size, emissive, emissiveIntensity, texturePat
{/* Saturn Rings */}
{body.id === '699' && <SaturnRings />}
{/* Sun glow effect */}
{/* Sun glow effect - multi-layer scattered light */}
{body.type === 'star' && (
<>
<pointLight intensity={10} distance={400} color="#fff8e7" />
{/* Inner bright corona */}
<mesh>
<sphereGeometry args={[size * 1.3, 32, 32]} />
<meshBasicMaterial
color="#FDB813"
transparent
opacity={0.6}
blending={THREE.AdditiveBlending}
depthWrite={false}
/>
</mesh>
{/* Middle glow layer */}
<mesh>
<sphereGeometry args={[size * 1.8, 32, 32]} />
<meshBasicMaterial color="#FDB813" transparent opacity={0.35} />
<meshBasicMaterial
color="#FDB813"
transparent
opacity={0.3}
blending={THREE.AdditiveBlending}
depthWrite={false}
/>
</mesh>
{/* Outer diffuse halo */}
<mesh>
<sphereGeometry args={[size * 2.5, 32, 32]} />
<meshBasicMaterial
color="#FFD700"
transparent
opacity={0.15}
blending={THREE.AdditiveBlending}
depthWrite={false}
/>
</mesh>
{/* Far scattered light */}
<mesh>
<sphereGeometry args={[size * 3.5, 32, 32]} />
<meshBasicMaterial
color="#FFA500"
transparent
opacity={0.08}
blending={THREE.AdditiveBlending}
depthWrite={false}
/>
</mesh>
</>
)}

View File

@ -355,13 +355,57 @@ function PlanetMesh({ body, size, emissive, emissiveIntensity, scaledPos, textur
{/* Saturn Rings */}
{body.id === '699' && <SaturnRings />}
{/* Sun glow effect */}
{/* Sun glow effect - multi-layer scattered light */}
{body.type === 'star' && (
<>
<pointLight intensity={10} distance={400} color="#fff8e7" />
{/* Inner bright corona */}
<mesh>
<sphereGeometry args={[size * 1.3, 32, 32]} />
<meshBasicMaterial
color="#FDB813"
transparent
opacity={0.6}
blending={THREE.AdditiveBlending}
depthWrite={false}
/>
</mesh>
{/* Middle glow layer */}
<mesh>
<sphereGeometry args={[size * 1.8, 32, 32]} />
<meshBasicMaterial color="#FDB813" transparent opacity={0.35} />
<meshBasicMaterial
color="#FDB813"
transparent
opacity={0.3}
blending={THREE.AdditiveBlending}
depthWrite={false}
/>
</mesh>
{/* Outer diffuse halo */}
<mesh>
<sphereGeometry args={[size * 2.5, 32, 32]} />
<meshBasicMaterial
color="#FFD700"
transparent
opacity={0.15}
blending={THREE.AdditiveBlending}
depthWrite={false}
/>
</mesh>
{/* Far scattered light */}
<mesh>
<sphereGeometry args={[size * 3.5, 32, 32]} />
<meshBasicMaterial
color="#FFA500"
transparent
opacity={0.08}
blending={THREE.AdditiveBlending}
depthWrite={false}
/>
</mesh>
</>
)}
@ -369,18 +413,18 @@ function PlanetMesh({ body, size, emissive, emissiveIntensity, scaledPos, textur
{/* Name label using CanvasTexture */}
{labelTexture && (
<Billboard
position={[0, size + 1.0, 0]} // Raised slightly
position={[0, size + 0.8, 0]} // Slightly closer to body
follow={true}
lockX={false}
lockY={false}
lockZ={false}
>
<mesh scale={[2.5, 1.25, 1]}>
<mesh scale={[1.8, 0.9, 1]}>
<planeGeometry />
<meshBasicMaterial
map={labelTexture}
transparent
opacity={isSelected ? 1 : 0.6}
<meshBasicMaterial
map={labelTexture}
transparent
opacity={isSelected ? 1 : 0.6}
depthWrite={false}
toneMapped={false} // Keep colors bright
/>

View File

@ -1,5 +1,5 @@
import { useState, useEffect } from 'react';
import { ChevronLeft, ChevronRight, ChevronDown, ChevronUp, Search, Globe, Rocket, Moon, Asterisk, Sparkles } from 'lucide-react';
import { ChevronLeft, ChevronRight, ChevronDown, ChevronUp, Search, Globe, Rocket, Moon, Asterisk, Sparkles, Star } from 'lucide-react';
import type { CelestialBody } from '../types';
interface ProbeListProps {
@ -38,9 +38,8 @@ export function ProbeList({ probes, planets, onBodySelect, selectedBody, onReset
if (!b.positions || b.positions.length === 0) {
return false;
}
// Filter by search term and type
return (b.name_zh || b.name).toLowerCase().includes(searchTerm.toLowerCase()) &&
b.type !== 'star'; // Exclude Sun from list
// Filter by search term - include all types including stars
return (b.name_zh || b.name).toLowerCase().includes(searchTerm.toLowerCase());
})
.map(body => ({
body,
@ -53,6 +52,7 @@ export function ProbeList({ probes, planets, onBodySelect, selectedBody, onReset
const allBodies = [...planets, ...probes];
const processedBodies = processBodies(allBodies);
const starList = processedBodies.filter(({ body }) => body.type === 'star');
const planetList = processedBodies.filter(({ body }) => body.type === 'planet');
const dwarfPlanetList = processedBodies.filter(({ body }) => body.type === 'dwarf_planet');
const satelliteList = processedBodies.filter(({ body }) => body.type === 'satellite');
@ -107,6 +107,20 @@ export function ProbeList({ probes, planets, onBodySelect, selectedBody, onReset
{/* List Content */}
<div className="flex-1 overflow-y-auto custom-scrollbar p-2 space-y-2">
{/* Stars Group */}
{starList.length > 0 && (
<BodyGroup
title="恒星"
icon={<Star size={12} />}
count={starList.length}
bodies={starList}
isExpanded={expandedGroup === 'star'}
onToggle={() => toggleGroup('star')}
selectedBody={selectedBody}
onBodySelect={onBodySelect}
/>
)}
{/* Planets Group */}
{planetList.length > 0 && (
<BodyGroup

View File

@ -483,7 +483,7 @@ export function CelestialBodies() {
/>
)}
</Tabs.TabPane>
<Tabs.TabPane tab="详细信息 (Markdown)" key="details">
<Tabs.TabPane tab="详细信息" key="details">
<Form.Item name="details" style={{ marginBottom: 0 }}>
<MdEditor
value={form.getFieldValue('details')}

View File

@ -240,20 +240,14 @@ export function SystemSettings() {
styles={{ body: { padding: 16 } }}
>
<Alert
title="重要操作说明"
title="清除缓存会清空所有内存缓存和 Redis 缓存,包括:"
description={
<div>
<p style={{ marginBottom: 8 }}>
Redis
</p>
<ul style={{ marginBottom: 0, paddingLeft: 20 }}>
<li></li>
<li>NASA API </li>
<li></li>
<li>* </li>
<li>* NASA API </li>
<li>* </li>
</ul>
<p style={{ marginTop: 8, marginBottom: 0, color: '#fa8c16' }}>
<WarningOutlined /> NASA API
</p>
</div>
}
type="warning"
@ -279,10 +273,6 @@ export function SystemSettings() {
</Button>
</Popconfirm>
<Button icon={<ReloadOutlined />} onClick={loadData}>
</Button>
</Space>
</Card>

View File

@ -27,37 +27,37 @@ export function createLabelTexture(name: string, subtext: string | null, distanc
// Center the single name vertically
ctx.textBaseline = 'middle';
// Use a slightly larger font for simple labels (like stars/constellations)
ctx.font = 'bold 72px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Microsoft YaHei", sans-serif';
ctx.font = 'bold 48px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Microsoft YaHei", sans-serif';
ctx.fillStyle = color;
ctx.fillText(name, 256, 128);
} else {
// Complex Label Mode (Name + Subtext + Distance)
ctx.textBaseline = 'top';
// 1. Name (Large, Top)
ctx.font = 'bold 64px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Microsoft YaHei", sans-serif';
ctx.font = 'bold 42px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Microsoft YaHei", sans-serif';
ctx.fillStyle = color;
ctx.fillText(name, 256, 10);
let nextY = 90;
let nextY = 65;
// 2. Subtext/Offset (Medium, Middle)
if (subtext) {
ctx.font = 'bold 42px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Microsoft YaHei", sans-serif';
ctx.font = 'bold 32px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Microsoft YaHei", sans-serif';
ctx.fillStyle = '#ffaa00'; // Orange for warning/info
ctx.fillText(subtext, 256, nextY);
nextY += 60;
nextY += 48;
} else {
// Add some spacing if no subtext
nextY += 20;
nextY += 15;
}
// 3. Distance (Small, Bottom)
if (distance) {
ctx.font = '36px "SF Mono", "Roboto Mono", Menlo, monospace';
ctx.font = '28px "SF Mono", "Roboto Mono", Menlo, monospace';
ctx.fillStyle = color;
// Reduce opacity for distance to make it less distracting
ctx.globalAlpha = 0.7;
ctx.globalAlpha = 0.7;
ctx.fillText(distance, 256, nextY);
}
}