Add 3D solar system view with Threlte and velocity tails in 2D

- 3D scene: Threlte/Three.js with starfield, orbit lines, planet meshes,
  sun glow, OrbitControls (orbit/zoom/pan), point lighting
- 2D velocity tails: gradient lines showing planet velocity direction/magnitude
- New WASM API: get_body_velocities_at_epoch(), get_orbit_points()
- Orbit ellipses computed in Rust, rendered in both 2D and 3D views
- Ecliptic-to-Three.js coordinate mapping (Y-up convention)
- View toggle now switches between working 2D and 3D renderers

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-08 11:54:31 -07:00
parent 5efe0736ac
commit 067ef1f557
8 changed files with 258 additions and 10 deletions

View File

@@ -15,6 +15,7 @@ export class SolarSystem2DRenderer {
private camera: Camera2D = { x: 0, y: 0, zoom: 1 };
private bodyInfos: BodyInfo[] = [];
private positions: Float64Array = new Float64Array(0);
private velocities: Float64Array = new Float64Array(0);
private orbitPoints: Map<number, Float64Array> = new Map();
private isDragging = false;
private lastMouse = { x: 0, y: 0 };
@@ -66,9 +67,10 @@ export class SolarSystem2DRenderer {
];
}
updateBodies(infos: BodyInfo[], positions: Float64Array) {
updateBodies(infos: BodyInfo[], positions: Float64Array, velocities?: Float64Array) {
this.bodyInfos = infos;
this.positions = positions;
if (velocities) this.velocities = velocities;
}
updateOrbit(bodyId: number, points: Float64Array) {
@@ -108,7 +110,7 @@ export class SolarSystem2DRenderer {
if (i === 0) {
this.drawSun(x, y, info);
} else {
this.drawBody(x, y, info);
this.drawBody(i, x, y, info);
}
}
}
@@ -213,7 +215,7 @@ export class SolarSystem2DRenderer {
ctx.fillText(info.name, sx + 10, sy + 4);
}
private drawBody(xAU: number, yAU: number, info: BodyInfo) {
private drawBody(bodyIndex: number, xAU: number, yAU: number, info: BodyInfo) {
const ctx = this.ctx;
const [sx, sy] = this.auToScreen(xAU, yAU);
const [r, g, b] = info.color;
@@ -223,6 +225,32 @@ export class SolarSystem2DRenderer {
// Moons are smaller
if (info.radius_km < 3000) size = 1.5;
// Velocity tail
if (this.velocities.length > bodyIndex * 3 + 2) {
const vx = this.velocities[bodyIndex * 3];
const vy = this.velocities[bodyIndex * 3 + 1];
const speed = Math.sqrt(vx * vx + vy * vy);
if (speed > 1e-10) {
// Scale tail length: normalize velocity and multiply by a visual factor
const tailScale = this.getScale() * 0.15; // pixels per (AU/day)
const tx = sx + vx * tailScale;
const ty = sy - vy * tailScale; // flip Y
// Gradient tail from body color to transparent
const gradient = ctx.createLinearGradient(sx, sy, tx, ty);
gradient.addColorStop(0, `rgba(${r}, ${g}, ${b}, 0.6)`);
gradient.addColorStop(1, `rgba(${r}, ${g}, ${b}, 0)`);
ctx.strokeStyle = gradient;
ctx.lineWidth = size * 0.8;
ctx.lineCap = 'round';
ctx.beginPath();
ctx.moveTo(sx, sy);
ctx.lineTo(tx, ty);
ctx.stroke();
}
}
// Body dot
ctx.fillStyle = rgbToCss(r, g, b);
ctx.beginPath();