Compare commits
3 Commits
ed7f2458f7
...
3d6a57cd0b
| Author | SHA1 | Date |
|---|---|---|
|
|
3d6a57cd0b | |
|
|
db679640bc | |
|
|
fba2923c92 |
|
|
@ -0,0 +1,14 @@
|
|||
# Resources
|
||||
资源文件/
|
||||
|
||||
# OS files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Environments
|
||||
.env*
|
||||
!.env.example
|
||||
|
||||
# IDEs
|
||||
.vscode/
|
||||
.idea/
|
||||
|
|
@ -67,6 +67,123 @@ class HorizonsService:
|
|||
logger.error(f"Error fetching raw data for {body_id}: {repr(e)}")
|
||||
raise
|
||||
|
||||
async def search_body_by_name(self, name: str, db: AsyncSession = None) -> dict:
|
||||
"""
|
||||
Search for a celestial body in Horizons by name.
|
||||
|
||||
Args:
|
||||
name: Name to search (e.g. 'Ceres', 'Halley')
|
||||
db: Database session (optional, for future caching)
|
||||
|
||||
Returns:
|
||||
Dict with success/error and data
|
||||
"""
|
||||
url = "https://ssd.jpl.nasa.gov/api/horizons.api"
|
||||
# Using a wildcard search command
|
||||
cmd_val = f"'{name}*'"
|
||||
|
||||
params = {
|
||||
"format": "text",
|
||||
"COMMAND": cmd_val,
|
||||
"OBJ_DATA": "YES",
|
||||
"MAKE_EPHEM": "NO"
|
||||
}
|
||||
|
||||
try:
|
||||
client_kwargs = {"timeout": settings.nasa_api_timeout}
|
||||
if settings.proxy_dict:
|
||||
client_kwargs["proxies"] = settings.proxy_dict
|
||||
|
||||
async with httpx.AsyncClient(**client_kwargs) as client:
|
||||
logger.info(f"Searching Horizons for: {name}")
|
||||
response = await client.get(url, params=params)
|
||||
|
||||
if response.status_code != 200:
|
||||
return {"success": False, "error": f"NASA API Error: {response.status_code}"}
|
||||
|
||||
text = response.text
|
||||
|
||||
# Case 1: Direct match (Horizon returns data directly)
|
||||
# Look for "Target body name:" or similar indicators of a resolved body
|
||||
if "Target body name:" in text or "Physical properties" in text:
|
||||
# Extract ID and Name
|
||||
# Pattern: "Target body name: 1 Ceres (A801 AA)" or similar
|
||||
match = re.search(r"Target body name:\s*(.*?)\s*\{", text)
|
||||
if not match:
|
||||
match = re.search(r"Target body name:\s*(.*?)\n", text)
|
||||
|
||||
full_name = match.group(1).strip() if match else name
|
||||
|
||||
# Try to extract ID from the text, usually in the header or COMMAND output
|
||||
# This is tricky with raw text, but let's try to extract from the command echo
|
||||
# Or we can just use the input name if we can't find a better ID
|
||||
# Ideally we want the numeric ID (e.g. '1' for Ceres, '399' for Earth)
|
||||
|
||||
# If it's a direct match, the ID might not be explicitly listed as "ID = ..."
|
||||
# But often the name contains it, e.g. "1 Ceres"
|
||||
body_id = name # Fallback
|
||||
|
||||
# Try to parse "1 Ceres" -> id=1
|
||||
id_match = re.search(r"^(\d+)\s+([a-zA-Z]+)", full_name)
|
||||
if id_match:
|
||||
body_id = id_match.group(1)
|
||||
clean_name = id_match.group(2)
|
||||
else:
|
||||
clean_name = full_name
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"id": body_id,
|
||||
"name": clean_name,
|
||||
"full_name": full_name
|
||||
}
|
||||
|
||||
# Case 2: Multiple matches (Ambiguous)
|
||||
# Horizons returns a list of matches
|
||||
if "Multiple major-bodies match" in text or "Matching small-bodies" in text:
|
||||
# We need to parse the list and pick the best match or return the first one
|
||||
# For now, let's try to find the most likely match (exact name)
|
||||
|
||||
# Pattern for small bodies: "record # epoch-yr primary desig >name<"
|
||||
# or " ID Name Designation"
|
||||
|
||||
# Simple heuristic: Look for lines containing the name
|
||||
lines = text.split('\n')
|
||||
best_match = None
|
||||
|
||||
for line in lines:
|
||||
if name.lower() in line.lower():
|
||||
# Try to extract ID (first column usually)
|
||||
parts = line.strip().split()
|
||||
if parts and parts[0].isdigit() or (parts[0].startswith('-') and parts[0][1:].isdigit()):
|
||||
best_match = {
|
||||
"id": parts[0],
|
||||
"name": name,
|
||||
"full_name": line.strip()
|
||||
}
|
||||
break
|
||||
|
||||
if best_match:
|
||||
return {
|
||||
"success": True,
|
||||
"id": best_match["id"],
|
||||
"name": best_match["name"],
|
||||
"full_name": best_match["full_name"]
|
||||
}
|
||||
|
||||
return {"success": False, "error": "Multiple matches found, please be more specific"}
|
||||
|
||||
# Case 3: No match
|
||||
if "No matches found" in text:
|
||||
return {"success": False, "error": "No celestial body found with that name"}
|
||||
|
||||
# Fallback for unknown response format
|
||||
return {"success": False, "error": "Could not parse NASA response"}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Search error for {name}: {repr(e)}")
|
||||
return {"success": False, "error": str(e)}
|
||||
|
||||
async def get_body_positions(
|
||||
self,
|
||||
body_id: str,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,390 @@
|
|||
# Cosmo Frontend Asset Loading Strategy Analysis
|
||||
|
||||
## Executive Summary
|
||||
|
||||
The Cosmo frontend project implements a 3D solar system visualization using Three.js and React-Three-Fiber. The asset loading strategy currently uses a mix of on-demand fetching and preloading patterns, with assets served through a backend API proxy.
|
||||
|
||||
---
|
||||
|
||||
## 1. Asset Inventory
|
||||
|
||||
### 1.1 Texture Assets (Primary Location: Backend Upload)
|
||||
|
||||
**Backend Location**: `/Users/jiliu/WorkSpace/cosmo/backend/upload/texture/`
|
||||
- **Total Size**: ~19 MB
|
||||
- **Format**: Primarily JPEG with some PNG
|
||||
- **Built/Dist Location**: `/Users/jiliu/WorkSpace/cosmo/frontend/dist/textures/`
|
||||
|
||||
#### Key Texture Files (by size):
|
||||
|
||||
| File | Size | Purpose |
|
||||
|------|------|---------|
|
||||
| 2k_moon.jpg | 1.0 MB | Moon surface texture |
|
||||
| 2k_venus_surface.jpg | 868 KB | Venus surface |
|
||||
| 2k_mercury.jpg | 856 KB | Mercury surface |
|
||||
| 2k_sun.jpg | 804 KB | Sun/Star texture |
|
||||
| 2k_mars.jpg | 736 KB | Mars surface |
|
||||
| 2k_jupiter.jpg | 488 KB | Jupiter surface |
|
||||
| 2k_earth_daymap.jpg | 456 KB | Earth day side |
|
||||
| 2k_earth_nightmap.jpg | 256 KB | Earth night side |
|
||||
| 2k_stars_milky_way.jpg | 248 KB | Milky Way background |
|
||||
| 2k_neptune.jpg | 236 KB | Neptune surface |
|
||||
| 2k_venus_atmosphere.jpg | 228 KB | Venus atmosphere |
|
||||
| 2k_saturn.jpg | 196 KB | Saturn surface |
|
||||
| 2k_uranus.jpg | 76 KB | Uranus surface |
|
||||
| 2k_saturn_ring_alpha.png | 12 KB | Saturn ring transparency |
|
||||
|
||||
**Additional Textures** (comets, dwarf planets, moons):
|
||||
- 2k_pluto.jpg: 3.8 MB (largest single file)
|
||||
- Various comet/asteroid textures: 300-500 KB each
|
||||
- Moon textures (Ganymede, Io, Titan, Mimas): 350-360 KB each
|
||||
|
||||
---
|
||||
|
||||
### 1.2 3D Model Assets (GLB/GLTF Format)
|
||||
|
||||
**Backend Location**: `/Users/jiliu/WorkSpace/cosmo/backend/upload/model/`
|
||||
- **Total Size**: ~24 MB
|
||||
- **Format**: GLB (binary GLTF)
|
||||
|
||||
#### Key Model Files (by size):
|
||||
|
||||
| File | Size | Purpose |
|
||||
|------|------|---------|
|
||||
| juno.glb | 8.6 MB | Juno space probe |
|
||||
| cassini.glb | 1.6 MB | Cassini spacecraft |
|
||||
| voyager_2.glb | 1.6 MB | Voyager 2 probe |
|
||||
| parker_solar_probe.glb | 436 KB | Parker Solar Probe |
|
||||
| voyager_1.glb | 280 KB | Voyager 1 probe |
|
||||
| webb_space_telescope.glb | ~1-2 MB (from list) | JWST |
|
||||
| new_horizons.glb | ~1-2 MB (from list) | New Horizons probe |
|
||||
| perseverance.glb | ~1-2 MB (from list) | Mars Perseverance rover |
|
||||
|
||||
**Observations**:
|
||||
- Juno.glb (8.6 MB) is significantly larger than other models
|
||||
- Most probes range 280 KB to 1.6 MB
|
||||
- Total probe models: ~24 MB for ~8 spacecraft
|
||||
|
||||
---
|
||||
|
||||
## 2. Asset Loading Strategy
|
||||
|
||||
### 2.1 Texture Loading (Detailed Implementation)
|
||||
|
||||
**File**: `/Users/jiliu/WorkSpace/cosmo/frontend/src/components/CelestialBody.tsx`
|
||||
|
||||
```typescript
|
||||
// Current Loading Pattern:
|
||||
1. Component mounts (Planet component)
|
||||
2. fetchBodyResources(body.id, 'texture') - calls API
|
||||
3. Finds main texture (excludes 'atmosphere' and 'night')
|
||||
4. Constructs URL: /upload/{file_path}
|
||||
5. useTexture(texturePath) - Three.js texture loader
|
||||
6. Renders mesh with loaded texture
|
||||
|
||||
// Key Line:
|
||||
const texture = texturePath ? useTexture(texturePath) : null;
|
||||
```
|
||||
|
||||
**Loading Characteristics**:
|
||||
- **Type**: On-demand (per celestial body)
|
||||
- **Caching**: useTexture hook likely caches within session
|
||||
- **Error Handling**: Falls back to null (gray placeholder material)
|
||||
- **Performance**: No preloading - each body texture loads when component mounts
|
||||
|
||||
### 2.2 Model Loading (3D Probes)
|
||||
|
||||
**File**: `/Users/jiliu/WorkSpace/cosmo/frontend/src/components/Probe.tsx`
|
||||
|
||||
```typescript
|
||||
// Current Loading Pattern:
|
||||
1. Component mounts (Probe component)
|
||||
2. fetchBodyResources(body.id, 'model') - calls API
|
||||
3. Gets first model resource + scale metadata
|
||||
4. Constructs URL: /upload/{file_path}
|
||||
5. useGLTF.preload(fullPath) - PRELOADING ENABLED
|
||||
6. setModelPath(fullPath)
|
||||
7. useGLTF(modelPath) - loads and parses GLB
|
||||
|
||||
// Key Implementation:
|
||||
useGLTF.preload(fullPath); // Preload before rendering
|
||||
const gltf = useGLTF(modelPath); // Actual load
|
||||
```
|
||||
|
||||
**Loading Characteristics**:
|
||||
- **Type**: On-demand with optional preloading
|
||||
- **Preloading**: Yes - `useGLTF.preload()` is called (Line 281)
|
||||
- **Caching**: useGLTF hook caches in session
|
||||
- **Scale Handling**: Custom scale from `extra_data.scale`
|
||||
- **Fallback**: ProbeFallback component renders red sphere if load fails
|
||||
- **Performance**: Preload triggered immediately, improves perceived load time
|
||||
|
||||
### 2.3 Background/Static Assets
|
||||
|
||||
**File**: `/Users/jiliu/WorkSpace/cosmo/frontend/src/components/Scene.tsx`
|
||||
|
||||
```typescript
|
||||
// Procedural Assets (NO external files):
|
||||
1. BackgroundStars (radius=300, count=5000, procedural)
|
||||
2. AsteroidBelts (procedurally generated)
|
||||
3. Nebulae (procedurally generated)
|
||||
4. Constellations (data-driven but rendered procedurally)
|
||||
|
||||
// Data-Driven Assets (fetched from API):
|
||||
1. Stars (real star catalog data)
|
||||
2. Galaxies (CanvasTexture generated)
|
||||
3. Satellites/Planets (textures fetched on demand)
|
||||
```
|
||||
|
||||
**Observations**:
|
||||
- Extensive use of procedural generation reduces asset file count
|
||||
- Galaxy textures created with CanvasTexture (no disk files)
|
||||
- Star data loaded from API, rendered with simple geometry
|
||||
|
||||
---
|
||||
|
||||
## 3. Loading Flow & Architecture
|
||||
|
||||
### 3.1 Request Path
|
||||
|
||||
```
|
||||
Frontend Component
|
||||
↓
|
||||
fetchBodyResources(bodyId, type)
|
||||
↓
|
||||
API Call: /api/celestial/resources/{bodyId}?resource_type=texture|model
|
||||
↓
|
||||
Backend API (Port 8000)
|
||||
↓
|
||||
Vite Proxy Dev: localhost:8000/upload/{type}/{filename}
|
||||
OR
|
||||
Nginx Production: proxy to backend
|
||||
↓
|
||||
Actual File: /backend/upload/{type}/{filename}
|
||||
↓
|
||||
Response to Frontend
|
||||
↓
|
||||
useTexture() or useGLTF()
|
||||
↓
|
||||
Three.js Renderer
|
||||
```
|
||||
|
||||
### 3.2 API Integration Points
|
||||
|
||||
**File**: `/Users/jiliu/WorkSpace/cosmo/frontend/src/utils/api.ts`
|
||||
|
||||
```typescript
|
||||
// Fetch function signature:
|
||||
export async function fetchBodyResources(
|
||||
bodyId: string,
|
||||
resourceType?: string
|
||||
): Promise<{
|
||||
body_id: string;
|
||||
resources: Array<{
|
||||
id: number;
|
||||
resource_type: string;
|
||||
file_path: string; // e.g., "texture/2k_sun.jpg"
|
||||
file_size: number;
|
||||
mime_type: string;
|
||||
created_at: string;
|
||||
extra_data?: Record<string, any>; // scale, etc.
|
||||
}>;
|
||||
}>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Vite Build Configuration
|
||||
|
||||
**File**: `/Users/jiliu/WorkSpace/cosmo/frontend/vite.config.ts`
|
||||
|
||||
```typescript
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
server: {
|
||||
host: '0.0.0.0',
|
||||
port: 5173,
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://localhost:8000',
|
||||
changeOrigin: true,
|
||||
},
|
||||
'/upload': {
|
||||
target: 'http://localhost:8000',
|
||||
changeOrigin: true,
|
||||
},
|
||||
'/public': {
|
||||
target: 'http://localhost:8000',
|
||||
changeOrigin: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
**Current Configuration Analysis**:
|
||||
- **No explicit asset optimization**: No image compression, no WebP conversion
|
||||
- **No size limits**: All assets proxied without size constraints
|
||||
- **No chunking strategy**: Vite default chunks (1.2 MB JS bundle observed)
|
||||
- **Proxy strategy**: All assets proxied through backend (good for dynamic content)
|
||||
|
||||
---
|
||||
|
||||
## 5. Build Output Analysis
|
||||
|
||||
**Dist Directory**: `/Users/jiliu/WorkSpace/cosmo/frontend/dist/`
|
||||
- **Total Size**: ~20 MB
|
||||
- **JavaScript Bundle**: 1.2 MB (index-c2PfKiPB.js)
|
||||
- **CSS**: 9.8 KB
|
||||
- **Textures**: ~7.9 MB (in dist, smaller than backend copy)
|
||||
- **Models**: ~12.8 MB (in dist)
|
||||
|
||||
**Key Finding**: Textures in dist folder are smaller than backend source, suggesting some optimization (JPEG compression) during build/deploy.
|
||||
|
||||
---
|
||||
|
||||
## 6. Lazy Loading & Code Splitting
|
||||
|
||||
**File**: `/Users/jiliu/WorkSpace/cosmo/frontend/src/App.tsx`
|
||||
|
||||
```typescript
|
||||
// Already Implemented:
|
||||
const InterstellarTicker = lazy(() =>
|
||||
import('./components/InterstellarTicker')
|
||||
.then(m => ({ default: m.InterstellarTicker }))
|
||||
);
|
||||
|
||||
const MessageBoard = lazy(() =>
|
||||
import('./components/MessageBoard')
|
||||
.then(m => ({ default: m.MessageBoard }))
|
||||
);
|
||||
|
||||
const BodyDetailOverlay = lazy(() =>
|
||||
import('./components/BodyDetailOverlay')
|
||||
.then(m => ({ default: m.BodyDetailOverlay }))
|
||||
);
|
||||
|
||||
// Wrapped in Suspense:
|
||||
<Suspense fallback={null}>
|
||||
<InterstellarTicker />
|
||||
</Suspense>
|
||||
```
|
||||
|
||||
**Status**:
|
||||
- Lazy loading implemented for 3 non-critical components
|
||||
- Suspense fallback set to null (no skeleton loading)
|
||||
- No lazy loading for 3D rendering components (CelestialBody, Probe, Scene)
|
||||
|
||||
---
|
||||
|
||||
## 7. Current Optimization Opportunities
|
||||
|
||||
### 7.1 Identified Issues
|
||||
|
||||
1. **No Texture Compression**
|
||||
- Total texture size: 19 MB (backend) → 7.9 MB (dist)
|
||||
- JPEG quality likely could be reduced further
|
||||
- No WebP fallback for modern browsers
|
||||
- No LOD (Level of Detail) system
|
||||
|
||||
2. **Large Model File**
|
||||
- Juno.glb: 8.6 MB (30% of all model assets)
|
||||
- No model optimization/decimation
|
||||
- No LOD for probes
|
||||
- All vertices/triangles loaded even when zoomed out
|
||||
|
||||
3. **No Batch Asset Loading**
|
||||
- Each body texture loaded individually
|
||||
- 10+ API calls for visible celestial bodies
|
||||
- No batching or parallel loading optimization
|
||||
- No request deduplication
|
||||
|
||||
4. **All-or-Nothing Texture Loading**
|
||||
- Texture loading blocks render until complete
|
||||
- Falls back to gray if load fails
|
||||
- No progressive/streaming load
|
||||
|
||||
5. **Limited Caching Strategy**
|
||||
- useTexture/useGLTF cache only within session
|
||||
- No persistent browser cache headers optimization
|
||||
- No service worker caching
|
||||
|
||||
6. **Background Star Data**
|
||||
- Stars component loads 1000+ star systems per API call
|
||||
- No pagination or frustum culling
|
||||
- All stars rendered regardless of visibility
|
||||
|
||||
### 7.2 Existing Optimizations
|
||||
|
||||
```
|
||||
POSITIVE:
|
||||
✓ Procedural generation for backgrounds (saves ~10 MB+ in textures)
|
||||
✓ useGLTF.preload() for probe models
|
||||
✓ useTexture hook caching
|
||||
✓ Lazy loading for non-critical UI components
|
||||
✓ Reused geometry for identical objects (stars use single SphereGeometry)
|
||||
✓ Billboard optimization for labels (distance culling)
|
||||
✓ Selective texture filtering (excludes atmosphere/night variants)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Performance Summary
|
||||
|
||||
### Asset Totals:
|
||||
- **Textures**: 19 MB (backend) / 7.9 MB (dist)
|
||||
- **Models**: 24 MB (backend) / 12.8 MB (dist)
|
||||
- **JavaScript**: 1.2 MB
|
||||
- **Total Transfer**: ~32 MB initial + on-demand loading
|
||||
|
||||
### Loading Pattern:
|
||||
- **Initial Load**: 1.2 MB (JS) + procedural background rendering
|
||||
- **Per-Scene**: ~100-500 KB (depending on visible bodies)
|
||||
- **Per-Probe**: 280 KB - 8.6 MB (first load)
|
||||
|
||||
### Current Bottlenecks:
|
||||
1. Juno probe (8.6 MB) loading time
|
||||
2. Sequential texture API calls
|
||||
3. No partial/progressive loading
|
||||
4. Large JPEG files (Moon: 1 MB, Venus: 868 KB)
|
||||
|
||||
---
|
||||
|
||||
## 9. File Locations Summary
|
||||
|
||||
### Source Code:
|
||||
- **Main 3D Scene**: `/Users/jiliu/WorkSpace/cosmo/frontend/src/components/Scene.tsx`
|
||||
- **Texture Loading**: `/Users/jiliu/WorkSpace/cosmo/frontend/src/components/CelestialBody.tsx` (Line 102-123)
|
||||
- **Model Loading**: `/Users/jiliu/WorkSpace/cosmo/frontend/src/components/Probe.tsx` (Line 261-295)
|
||||
- **API Integration**: `/Users/jiliu/WorkSpace/cosmo/frontend/src/utils/api.ts`
|
||||
- **Build Config**: `/Users/jiliu/WorkSpace/cosmo/frontend/vite.config.ts`
|
||||
|
||||
### Asset Locations:
|
||||
- **Texture Source**: `/Users/jiliu/WorkSpace/cosmo/backend/upload/texture/`
|
||||
- **Model Source**: `/Users/jiliu/WorkSpace/cosmo/backend/upload/model/`
|
||||
- **Texture Build Output**: `/Users/jiliu/WorkSpace/cosmo/frontend/dist/textures/`
|
||||
- **Model Build Output**: `/Users/jiliu/WorkSpace/cosmo/frontend/dist/models/`
|
||||
- **Documentation**: `/Users/jiliu/WorkSpace/cosmo/frontend/PERFORMANCE_OPTIMIZATION.md`
|
||||
|
||||
---
|
||||
|
||||
## 10. Recommendations (Priority Order)
|
||||
|
||||
### Quick Wins (1-2 hours):
|
||||
1. Implement image compression (JPEG quality: 80-85%)
|
||||
2. Add WebP format with JPEG fallback
|
||||
3. Set proper Cache-Control headers on backend
|
||||
4. Implement batch/parallel texture loading
|
||||
|
||||
### Medium Term (4-8 hours):
|
||||
1. Add texture LOD (Load specific resolution by distance)
|
||||
2. Implement model decimation for Juno probe
|
||||
3. Add progressive texture loading (low-res → high-res)
|
||||
4. Implement frustum culling for star systems
|
||||
|
||||
### Long Term (16+ hours):
|
||||
1. Implement texture atlas for small textures
|
||||
2. Add KTX2/Basis texture compression
|
||||
3. Implement virtual scrolling for star catalog
|
||||
4. Add service worker for persistent caching
|
||||
|
||||
|
|
@ -9,7 +9,7 @@ import { useTexture, Billboard } from '@react-three/drei';
|
|||
import type { CelestialBody as CelestialBodyType } from '../types';
|
||||
import { calculateRenderPosition, getOffsetDescription } from '../utils/renderPosition';
|
||||
import { fetchBodyResources } from '../utils/api';
|
||||
import { PLANET_SIZES, SATELLITE_SIZES, getCelestialSize } from '../config/celestialSizes';
|
||||
import { getCelestialSize } from '../config/celestialSizes';
|
||||
import { createLabelTexture } from '../utils/labelTexture';
|
||||
|
||||
interface CelestialBodyProps {
|
||||
|
|
|
|||
|
|
@ -6,64 +6,31 @@
|
|||
/**
|
||||
* Planet rendering sizes (radius in scene units)
|
||||
*/
|
||||
export const PLANET_SIZES: Record<string, number> = {
|
||||
Mercury: 0.35,
|
||||
Venus: 0.55,
|
||||
Earth: 0.6,
|
||||
Mars: 0.45,
|
||||
Jupiter: 1.4,
|
||||
Saturn: 1.2,
|
||||
Uranus: 0.8,
|
||||
Neptune: 0.8,
|
||||
Pluto: 0.2,
|
||||
// Default for unknown planets
|
||||
/**
|
||||
* Celestial body rendering sizes configuration
|
||||
* Shared across components for consistent sizing
|
||||
*
|
||||
* STRICT RULE: Sizes are determined solely by body TYPE, not by name.
|
||||
* This ensures consistent visualization scales across categories.
|
||||
*/
|
||||
|
||||
export const TYPE_SIZES: Record<string, number> = {
|
||||
star: 0.4, // Base mesh size (glow effects make it look larger)
|
||||
planet: 0.6, // Unified size for all planets (Earth-like)
|
||||
dwarf_planet: 0.18,
|
||||
satellite: 0.12,
|
||||
comet: 0.12,
|
||||
asteroid: 0.10,
|
||||
probe: 0.1,
|
||||
|
||||
// Fallback
|
||||
default: 0.5,
|
||||
};
|
||||
|
||||
/**
|
||||
* Satellite rendering sizes (radius in scene units)
|
||||
*/
|
||||
export const SATELLITE_SIZES: Record<string, number> = {
|
||||
Moon: 0.15,
|
||||
// Default for unknown satellites
|
||||
default: 0.12,
|
||||
};
|
||||
|
||||
/**
|
||||
* Star rendering sizes (radius in scene units)
|
||||
*/
|
||||
export const STAR_SIZES: Record<string, number> = {
|
||||
Sun: 0.4,
|
||||
// Default for unknown stars
|
||||
default: 0.4,
|
||||
};
|
||||
|
||||
/**
|
||||
* Comet rendering sizes (radius in scene units)
|
||||
* Comets are typically small with a bright nucleus
|
||||
*/
|
||||
export const COMET_SIZES: Record<string, number> = {
|
||||
// Famous comets
|
||||
Halley: 0.15,
|
||||
// Default for unknown comets
|
||||
default: 0.12,
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the rendering size for a celestial body by name and type
|
||||
* Get the rendering size for a celestial body by type only
|
||||
*/
|
||||
export function getCelestialSize(name: string, type: string): number {
|
||||
switch (type) {
|
||||
case 'planet':
|
||||
case 'dwarf_planet':
|
||||
return PLANET_SIZES[name] || PLANET_SIZES.default;
|
||||
case 'satellite':
|
||||
return SATELLITE_SIZES[name] || SATELLITE_SIZES.default;
|
||||
case 'star':
|
||||
return STAR_SIZES[name] || STAR_SIZES.default;
|
||||
case 'comet':
|
||||
return COMET_SIZES[name] || COMET_SIZES.default;
|
||||
default:
|
||||
return 0.5;
|
||||
}
|
||||
return TYPE_SIZES[type] || TYPE_SIZES.default;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -425,12 +425,12 @@ export function CelestialBodies() {
|
|||
dataIndex: 'type',
|
||||
key: 'type',
|
||||
filters: [
|
||||
{ text: '行星', value: 'planet' },
|
||||
{ text: '恒星', value: 'star' },
|
||||
{ text: '卫星', value: 'satellite' },
|
||||
{ text: '探测器', value: 'probe' },
|
||||
{ text: '行星', value: 'planet' },
|
||||
{ text: '矮行星', value: 'dwarf_planet' },
|
||||
{ text: '卫星', value: 'satellite' },
|
||||
{ text: '彗星', value: 'comet' },
|
||||
{ text: '探测器', value: 'probe' },
|
||||
],
|
||||
onFilter: (value, record) => record.type === value,
|
||||
render: (type: string) => {
|
||||
|
|
@ -582,12 +582,12 @@ export function CelestialBodies() {
|
|||
rules={[{ required: true, message: '请选择类型' }]}
|
||||
>
|
||||
<Select>
|
||||
<Select.Option value="star">恒星</Select.Option>
|
||||
<Select.Option value="planet">行星</Select.Option>
|
||||
<Select.Option value="dwarf_planet">矮行星</Select.Option>
|
||||
<Select.Option value="satellite">卫星</Select.Option>
|
||||
<Select.Option value="probe">探测器</Select.Option>
|
||||
<Select.Option value="star">恒星</Select.Option>
|
||||
<Select.Option value="comet">彗星</Select.Option>
|
||||
<Select.Option value="probe">探测器</Select.Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
|
|
@ -800,12 +800,12 @@ export function CelestialBodies() {
|
|||
rules={[{ required: true, message: '请选择类型' }]}
|
||||
>
|
||||
<Select>
|
||||
<Select.Option value="star">恒星</Select.Option>
|
||||
<Select.Option value="planet">行星</Select.Option>
|
||||
<Select.Option value="dwarf_planet">矮行星</Select.Option>
|
||||
<Select.Option value="satellite">卫星</Select.Option>
|
||||
<Select.Option value="probe">探测器</Select.Option>
|
||||
<Select.Option value="star">恒星</Select.Option>
|
||||
<Select.Option value="comet">彗星</Select.Option>
|
||||
<Select.Option value="probe">探测器</Select.Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
|
|
|
|||
|
|
@ -83,9 +83,12 @@ export function NASADownload() {
|
|||
planet: '行星',
|
||||
dwarf_planet: '矮行星',
|
||||
satellite: '卫星',
|
||||
comet: '彗星',
|
||||
probe: '探测器',
|
||||
};
|
||||
|
||||
const typeOrder = ['star', 'planet', 'dwarf_planet', 'satellite', 'comet', 'probe'];
|
||||
|
||||
useEffect(() => {
|
||||
loadBodies();
|
||||
}, []);
|
||||
|
|
@ -451,41 +454,46 @@ export function NASADownload() {
|
|||
>
|
||||
<Collapse
|
||||
defaultActiveKey={[]}
|
||||
items={Object.entries(bodies).map(([type, typeBodies]) => ({
|
||||
key: type,
|
||||
label: (
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<span>{typeNames[type] || type}</span>
|
||||
<Checkbox
|
||||
checked={typeBodies.every(b => selectedBodies.includes(b.id))}
|
||||
indeterminate={
|
||||
typeBodies.some(b => selectedBodies.includes(b.id)) &&
|
||||
!typeBodies.every(b => selectedBodies.includes(b.id))
|
||||
}
|
||||
onChange={(e) => {
|
||||
e.stopPropagation();
|
||||
handleTypeSelectAll(type, e.target.checked);
|
||||
}}
|
||||
>
|
||||
全选
|
||||
</Checkbox>
|
||||
</div>
|
||||
),
|
||||
children: (
|
||||
<Space orientation="vertical" style={{ width: '100%' }}>
|
||||
{typeBodies.map((body) => (
|
||||
<Checkbox
|
||||
key={body.id}
|
||||
checked={selectedBodies.includes(body.id)}
|
||||
onChange={(e) => handleBodySelect(body.id, e.target.checked)}
|
||||
>
|
||||
{body.name_zh || body.name} ({body.id})
|
||||
{!body.is_active && <Badge status="default" text="(未激活)" style={{ marginLeft: 8 }} />}
|
||||
</Checkbox>
|
||||
))}
|
||||
</Space>
|
||||
),
|
||||
}))}
|
||||
items={typeOrder
|
||||
.filter(type => bodies[type] && bodies[type].length > 0)
|
||||
.map((type) => {
|
||||
const typeBodies = bodies[type];
|
||||
return {
|
||||
key: type,
|
||||
label: (
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<span>{typeNames[type] || type}</span>
|
||||
<Checkbox
|
||||
checked={typeBodies.every(b => selectedBodies.includes(b.id))}
|
||||
indeterminate={
|
||||
typeBodies.some(b => selectedBodies.includes(b.id)) &&
|
||||
!typeBodies.every(b => selectedBodies.includes(b.id))
|
||||
}
|
||||
onChange={(e) => {
|
||||
e.stopPropagation();
|
||||
handleTypeSelectAll(type, e.target.checked);
|
||||
}}
|
||||
>
|
||||
全选
|
||||
</Checkbox>
|
||||
</div>
|
||||
),
|
||||
children: (
|
||||
<Space orientation="vertical" style={{ width: '100%' }}>
|
||||
{typeBodies.map((body) => (
|
||||
<Checkbox
|
||||
key={body.id}
|
||||
checked={selectedBodies.includes(body.id)}
|
||||
onChange={(e) => handleBodySelect(body.id, e.target.checked)}
|
||||
>
|
||||
{body.name_zh || body.name} ({body.id})
|
||||
{!body.is_active && <Badge status="default" text="(未激活)" style={{ marginLeft: 8 }} />}
|
||||
</Checkbox>
|
||||
))}
|
||||
</Space>
|
||||
),
|
||||
};
|
||||
})}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
|
|
|
|||
BIN
资源文件/卫星小.jpeg
|
Before Width: | Height: | Size: 14 KiB |
BIN
资源文件/哈雷彗星小.jpeg
|
Before Width: | Height: | Size: 8.6 KiB |
BIN
资源文件/土星小.jpeg
|
Before Width: | Height: | Size: 13 KiB |
BIN
资源文件/地球-图标小.jpeg
|
Before Width: | Height: | Size: 15 KiB |
BIN
资源文件/地球小.jpeg
|
Before Width: | Height: | Size: 16 KiB |
BIN
资源文件/太阳小.jpeg
|
Before Width: | Height: | Size: 14 KiB |
BIN
资源文件/彗星小.jpeg
|
Before Width: | Height: | Size: 9.4 KiB |
BIN
资源文件/恒星小.jpeg
|
Before Width: | Height: | Size: 13 KiB |
BIN
资源文件/星系小.jpeg
|
Before Width: | Height: | Size: 30 KiB |
BIN
资源文件/木星-2小.jpeg
|
Before Width: | Height: | Size: 21 KiB |
BIN
资源文件/木星小.jpeg
|
Before Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 5.1 MiB |
|
Before Width: | Height: | Size: 654 KiB |
|
Before Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 4.2 MiB |
BIN
资源文件/海王星小.jpeg
|
Before Width: | Height: | Size: 11 KiB |
BIN
资源文件/火星小.jpeg
|
Before Width: | Height: | Size: 9.4 KiB |
BIN
资源文件/火星车小.jpeg
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 6.1 MiB |
BIN
资源文件/知识卡/土星.png
|
Before Width: | Height: | Size: 5.6 MiB |
BIN
资源文件/知识卡/地球.png
|
Before Width: | Height: | Size: 5.2 MiB |
BIN
资源文件/知识卡/天王星.png
|
Before Width: | Height: | Size: 5.8 MiB |
BIN
资源文件/知识卡/月球.png
|
Before Width: | Height: | Size: 6.0 MiB |
BIN
资源文件/知识卡/木星.png
|
Before Width: | Height: | Size: 5.1 MiB |
BIN
资源文件/知识卡/水星.png
|
Before Width: | Height: | Size: 5.2 MiB |
BIN
资源文件/知识卡/海王星.png
|
Before Width: | Height: | Size: 5.8 MiB |
BIN
资源文件/知识卡/火星.png
|
Before Width: | Height: | Size: 5.8 MiB |
BIN
资源文件/知识卡/金星.png
|
Before Width: | Height: | Size: 5.8 MiB |
BIN
资源文件/航天飞机小.jpeg
|
Before Width: | Height: | Size: 12 KiB |
BIN
资源文件/行星小.jpeg
|
Before Width: | Height: | Size: 14 KiB |
BIN
资源文件/通信卫星小.jpeg
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 10 KiB |