oneshotlm
Compare all models on this prompt →

3D solar system · minimax-m3

Using the pre-provided three.min.js (global THREE), build a 3D solar system with the sun and orbiting planets at varied speeds and sizes, orbit-drag camera controls, and a starfield.

⛨ sandboxed Open in new tab ↗
transcript · 8 messages
user

Using the pre-provided three.min.js (global THREE), build a 3D solar system with the sun and orbiting planets at varied speeds and sizes, orbit-drag camera controls, and a starfield.

assistant
thinking
The user wants a 3D solar system built with three.min.js. Let me plan this out:

1. Sun in the center (emissive, glowing)
2. Multiple planets orbiting at varied speeds and sizes
3. Orbit-drag camera controls (rotating around the scene)
4. Starfield background
5. All in one self-contained index.html file
6. Load three.min.js with a plain script tag

Let me first check the workspace.
bash {"command":"ls -la /workspace"}
toolResult

total 644 drwxrwxrwx 3 node node 96 Jul 22 17:34 . drwxr-xr-x 1 root root 4096 Jul 22 17:34 .. -rw-r--r-- 1 node node 603445 Jul 22 17:34 three.min.js

assistant
thinking
Good, three.min.js is in the workspace. Let me build the solar system. I'll design:

1. **Sun**: Large sphere with emissive material, plus a glow halo (sprite or additive sphere)
2. **Planets**: At least 8 planets with different:
   - Sizes
   - Orbit radii
   - Orbital speeds
   - Colors (each unique)
   - Some have axial tilt / rotation
3. **Orbits**: Visible thin ring lines for each planet's orbit
4. **Starfield**: ~3000-5000 points scattered on a large sphere, varied colors/sizes
5. **Camera controls**: Mouse drag to orbit (azimuth/polar), wheel to zoom
6. **Lighting**: Point light at sun position
7. **Background**: Black with stars

Let me write the code:

```js
// Setup scene, camera, renderer
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(60, innerWidth/innerHeight, 0.1, 5000);
camera.position.set(0, 80, 180);

const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(innerWidth, innerHeight);
renderer.setPixelRatio(devicePixelRatio);
document.body.appendChild(renderer.domElement);

// Controls - manual implementation for orbit-drag
let camTheta = 0;  // azimuth
let camPhi = Math.PI/2 - 0.3;  // polar (from top)
let camDist = 220;
let isDragging = false;
let lastX = 0, lastY = 0;

renderer.domElement.addEventListener('mousedown', (e) => {
  isDragging = true; lastX = e.clientX; lastY = e.clientY;
});
window.addEventListener('mouseup', () => isDragging = false);
window.addEventListener('mousemove', (e) => {
  if (!isDragging) return;
  camTheta -= (e.clientX - lastX) * 0.005;
  camPhi -= (e.clientY - lastY) * 0.005;
  camPhi = Math.max(0.1, Math.min(Math.PI - 0.1, camPhi));
  lastX = e.clientX; lastY = e.clientY;
});
renderer.domElement.addEventListener('wheel', (e) => {
  camDist *= (1 + e.deltaY * 0.001);
  camDist = Math.max(30, Math.min(1500, camDist));
  e.preventDefault();
}, { passive: false });

// Touch support
let lastTouchDist = 0;
let touchStart = null;
renderer.domElement.addEventListener('touchstart', (e) => {
  if (e.touches.length === 1) {
    isDragging = true;
    lastX = e.touches[0].clientX;
    lastY = e.touches[0].clientY;
  } else if (e.touches.length === 2) {
    isDragging = false;
    lastTouchDist = Math.hypot(
      e.touches[0].clientX - e.touches[1].clientX,
      e.touches[0].clientY - e.touches[1].clientY
    );
  }
});
renderer.domElement.addEventListener('touchmove', (e) => {
  e.preventDefault();
  if (e.touches.length === 1 && isDragging) {
    const t = e.touches[0];
    camTheta -= (t.clientX - lastX) * 0.005;
    camPhi -= (t.clientY - lastY) * 0.005;
    camPhi = Math.max(0.1, Math.min(Math.PI - 0.1, camPhi));
    lastX = t.clientX; lastY = t.clientY;
  } else if (e.touches.length === 2) {
    const d = Math.hypot(
      e.touches[0].clientX - e.touches[1].clientX,
      e.touches[0].clientY - e.touches[1].clientY
    );
    camDist *= (lastTouchDist / d);
    camDist = Math.max(30, Math.min(1500, camDist));
    lastTouchDist = d;
  }
}, { passive: false });
renderer.domElement.addEventListener('touchend', () => isDragging = false);

// Sun
const sunGeo = new THREE.SphereGeometry(8, 48, 48);
const sunMat = new THREE.MeshBasicMaterial({ color: 0xffdd44 });
const sun = new THREE.Mesh(sunGeo, sunMat);
scene.add(sun);

// Sun glow - additive sphere
const glowGeo = new THREE.SphereGeometry(14, 32, 32);
const glowMat = new THREE.MeshBasicMaterial({ color: 0xffaa22, transparent: true, opacity: 0.25, side: THREE.BackSide, blending: THREE.AdditiveBlending });
const glow = new THREE.Mesh(glowGeo, glowMat);
scene.add(glow);

// Sun light
const sunLight = new THREE.PointLight(0xffffff, 2, 0, 1.5);
scene.add(sunLight);

// Planets
const planetDefs = [
  { name: 'Mercury', size: 0.8, dist: 18, speed: 1.6, color: 0xb0a090 },
  { name: 'Venus',   size: 1.4, dist: 26, speed: 1.1, color: 0xe8c280 },
  { name: 'Earth',   size: 1.5, dist: 36, speed: 1.0, color: 0x4488ff },
  { name: 'Mars',    size: 1.2, dist: 48, speed: 0.8, color: 0xff6644 },
  { name: 'Jupiter', size: 4.5, dist: 70, speed: 0.4, color: 0xd4a878 },
  { name: 'Saturn',  size: 3.8, dist: 95, speed: 0.3, color: 0xe8d090, ring: true },
  { name: 'Uranus',  size: 2.6, dist: 120, speed: 0.2, color: 0x88ddff },
  { name: 'Neptune', size: 2.4, dist: 145, speed: 0.15, color: 0x4466ff },
];

const planets = planetDefs.map(def => {
  const geo = new THREE.SphereGeometry(def.size, 32, 32);
  const mat = new THREE.MeshStandardMaterial({ color: def.color, roughness: 0.8, metalness: 0.1 });
  const mesh = new THREE.Mesh(geo, mat);
  
  // Pivot for orbit
  const pivot = new THREE.Object3D();
  pivot.add(mesh);
  mesh.position.x = def.dist;
  scene.add(pivot);
  
  // Orbit line
  const orbitPoints = [];
  const seg = 128;
  for (let i = 0; i <= seg; i++) {
    const a = (i / seg) * Math.PI * 2;
    orbitPoints.push(new THREE.Vector3(Math.cos(a) * def.dist, 0, Math.sin(a) * def.dist));
  }
  const orbitGeo = new THREE.BufferGeometry().setFromPoints(orbitPoints);
  const orbitMat = new THREE.LineBasicMaterial({ color: 0x444466, transparent: true, opacity: 0.4 });
  const orbit = new THREE.Line(orbitGeo, orbitMat);
  scene.add(orbit);
  
  // Saturn's ring
  if (def.ring) {
    const ringGeo = new THREE.RingGeometry(def.size * 1.4, def.size * 2.2, 64);
    const ringMat = new THREE.MeshBasicMaterial({ color: 0xd4b890, side: THREE.DoubleSide, transparent: true, opacity: 0.7 });
    const ring = new THREE.Mesh(ringGeo, ringMat);
    ring.rotation.x = Math.PI / 2.2;
    mesh.add(ring);
  }
  
  return { pivot, mesh, def, angle: Math.random() * Math.PI * 2 };
});

// Earth has a moon
const moon = (() => {
  const g = new THREE.SphereGeometry(0.4, 16, 16);
  const m = new THREE.MeshStandardMaterial({ color: 0xcccccc });
  const mesh = new THREE.Mesh(g, m);
  const pivot = new THREE.Object3D();
  // attach to earth
  const earth = planets[2];
  earth.mesh.add(pivot);
  pivot.add(mesh);
  mesh.position.x = 3;
  return { pivot, mesh };
})();

// Starfield
const starCount = 4000;
const starPositions = new Float32Array(starCount * 3);
const starColors = new Float32Array(starCount * 3);
for (let i = 0; i < starCount; i++) {
  // Uniform on sphere
  const u = Math.random(), v = Math.random();
  const theta = u * Math.PI * 2;
  const phi = Math.acos(2 * v - 1);
  const r = 1500 + Math.random() * 500;
  starPositions[i*3]   = r * Math.sin(phi) * Math.cos(theta);
  starPositions[i*3+1] = r * Math.sin(phi) * Math.sin(theta);
  starPositions[i*3+2] = r * Math.cos(phi);
  // Slight color variation
  const t = Math.random();
  if (t < 0.7) { starColors[i*3] = 1; starColors[i*3+1] = 1; starColors[i*3+2] = 1; }
  else if (t < 0.85) { starColors[i*3] = 1; starColors[i*3+1] = 0.8; starColors[i*3+2] = 0.6; }
  else { starColors[i*3] = 0.7; starColors[i*3+1] = 0.85; starColors[i*3+2] = 1; }
}
const starGeo = new THREE.BufferGeometry();
starGeo.setAttribute('position', new THREE.BufferAttribute(starPositions, 3));
starGeo.setAttribute('color', new THREE.BufferAttribute(starColors, 3));
const starMat = new THREE.PointsMaterial({ size: 1.5, vertexColors: true, sizeAttenuation: false, transparent: true, opacity: 0.9 });
const stars = new THREE.Points(starGeo, starMat);
scene.add(stars);

// Animation
const clock = new THREE.Clock();
function animate() {
  const dt = clock.getDelta();
  
  // Camera position from spherical coords
  camera.position.x = camDist * Math.sin(camPhi) * Math.cos(camTheta);
  camera.position.y = camDist * Math.cos(camPhi);
  camera.position.z = camDist * Math.sin(camPhi) * Math.sin(camTheta);
  camera.lookAt(0, 0, 0);
  
  // Rotate sun
  sun.rotation.y += dt * 0.2;
  glow.rotation.y -= dt * 0.1;
  
  // Animate planets
  planets.forEach(p => {
    p.angle += p.def.speed * dt * 0.3;
    p.pivot.rotation.y = p.angle;
    p.mesh.rotation.y += dt * 0.5;
  });
  
  // Moon
  moon.pivot.rotation.y += dt * 1.2;
  
  // Star twinkle - subtle rotation
  stars.rotation.y += dt * 0.005;
  
  renderer.render(scene, camera);
  requestAnimationFrame(animate);
}
animate();

window.addEventListener('resize', () => {
  camera.aspect = innerWidth / innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(innerWidth, innerHeight);
});
```

I'll also add a UI overlay showing controls and labels. Let me make the HTML self-contained with CSS. I want it to look polished with:
- Black background
- Title overlay
- Controls hint at bottom
- Maybe a "follow planet" feature would be nice but not required - let's keep it simple but polished

Let me also add planet labels using sprites or just an info panel that shows on hover? Actually, let me add a simple info panel showing planet names as we orbit, and use raycasting for picking. Or, simpler: add HTML overlay labels at planet positions using 3D-to-screen projection.

Actually for polish, let me add:
- Raycaster to show planet name when hovering
- Saturn ring
- Earth's moon
- Maybe asteroid belt between Mars and Jupiter (just visual)

Let me keep it focused. Hover-to-label is a nice touch. Let me also add a time speed slider.

Let me write the final file.
write /workspace/index.html (20742 bytes)
toolResult

Successfully wrote 20742 bytes to /workspace/index.html

assistant
thinking
Let me verify the file is correctly formed and there are no obvious syntax errors
bash {"command":"node -e \"\nconst fs = require('fs');\nconst html = fs.readFileSync('index.html', 'utf8');\n// Extract the inline script (the se
toolResult

Inline scripts found: 1 Inline JS parses OK three.min.js script tag: true File size: 20742 bytes

assistant
thinking
Everything looks good. The file:
- Has one inline script (the IIFE with all the logic)
- Three.min.js is loaded via `<script src="three.min.js">`
- The JS parses cleanly with no syntax errors
- All the required features are there:
  - Sun (with glow layers + point light)
  - 8 planets at varied sizes, distances, and speeds
  - Saturn has rings, Earth has a moon, asteroid belt between Mars and Jupiter
  - Orbit-drag camera controls (mouse + touch + pinch zoom + wheel)
  - Starfield with 5000 stars in varied colors and brightness
  - Extra polish: planet labels, hover tooltips, time speed control, reset key (R), toggle labels (L)

Let me give a brief summary of what was built.

Done. The single self-contained file is at `/workspace/index.html` (20.7 KB) and loads `three.min.js` via a plain `<script src="three.min.js">` tag. **What's in the scene** - **Sun** — emissive sphere with four layered additive glow halos, point light, slow self-rotation, and a subtle breathing pulse on the halos. - **Eight planets** — Mercury → Neptune, each with its own size, orbit radius, orbital speed, color, surface roughness, and spin. Speeds follow real ratios (Mercury is fastest, Neptune the slowest). Orbit lines drawn at low opacity on each planet's slightly-tilted plane. - **Earth's Moon** orbits Earth. - **Saturn's ring** built with a properly-mapped `RingGeometry`. - **Asteroid belt** of ~1400 points between Mars and Jupiter. - **Starfield** of 5000 stars on a sphere, color-varied (white, warm, orange, blue) and size-varied for depth. **Controls (orbit-drag camera)** - Drag (mouse or one finger) to orbit around the system; vertical drag clamped near the poles. - Scroll wheel or two-finger pinch to zoom (clamped 25–1400). - `R` resets the view; `L` toggles planet name labels. - Each planet also has a **hover tooltip** (raycaster picks the mesh, shows distance/speed/info) and a **floating HTML label** that tracks its screen position every frame. **UI panels** (top-left title, top-right time-speed slider with Pause/Normal/Fast presets, bottom-left control hints) use a translucent blurred-glass style and collapse sensibly on small screens. A **time-speed slider** lets you pause or speed up the simulation up to 3×. Open `index.html` directly in a browser — no network needed.

TAKE IT TO CODE

A single self-contained HTML file — copy it and open in any browser, or reuse the prompt with your own pi-agent run.

RUN

modelminimax-m3
vendorMiniMax
harnesspi-agent
generated2026-07-22

EVALUATION

4/5 working

3D solar system with sun, orbiting planets, rings on Saturn, starfield, and working drag/zoom/keyboard camera controls.

Vision grade · anthropic/claude-sonnet-5

MEASUREMENTS

Raw readings from the capture, not a verdict — the grade above is the only judgement. Percentages are how much of the screen changed; the measure under-reports sparse motion on a dark background.

idle motion1.5%
on click1.8%
on drag3.9%
on wheel13.1%
on enter + space3.2%
on arrow keys3.0%
on w/a/s/d3.6%
frame spread32.1 / 255
console errors0
js errors none

METRICS

runtime138.4s
tokens in13k
tokens out10k
cached14k
cost / run$0.0172