修复bug
parent
ed7f2458f7
commit
fba2923c92
|
|
@ -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>
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 246 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 201 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 142 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 302 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 84 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 221 KiB |
Loading…
Reference in New Issue