大幅调整的一个版本,待测试
parent
088529d6c4
commit
66fceb03de
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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 |
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
</>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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')}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue