Simulador Interactivo de Sistema Solar en 3D
De Wikiprompt, la enciclopedia libre de prompts
Simulador Interactivo de Sistema Solar en 3D Un prompt integral para construir un simulador 3D pulido del sistema solar con Three.js, que incluya órbitas realistas, controles, interfaz de usuario y mejoras visuales.
Contenido del PromptGuardar
🌐
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Interactive 3D Solar System</title>
<style>
body {
margin: 0;
overflow: hidden;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: radial-gradient(ellipse at center, #0a0e14 0%, #05070d 100%);
color: white;
}
#info {
position: absolute;
top: 20px;
left: 20px;
background: rgba(20, 25, 35, 0.7);
backdrop-filter: blur(10px);
padding: 12px 20px;
border-radius: 12px;
border: 1px solid rgba(255,255,255,0.1);
font-size: 14px;
letter-spacing: 0.5px;
box-shadow: 0 4px 20px rgba(0,0,0,0.5);
user-select: none;
pointer-events: none;
z-index: 10;
}
#info .label {
color: #8ab4f8;
margin-right: 6px;
}
#controls {
position: absolute;
bottom: 40px;
left: 50%;
transform: translateX(-50%);
display: flex;
align-items: center;
gap: 20px;
background: rgba(8, 15, 25, 0.75);
backdrop-filter: blur(15px);
padding: 15px 25px;
border-radius: 30px;
border: 1px solid rgba(255,255,255,0.1);
box-shadow: 0 8px 30px rgba(0,0,0,0.6);
z-index: 10;
}
#controls button {
background: #2563eb;
border: none;
color: white;
padding: 8px 18px;
border-radius: 20px;
cursor: pointer;
font-weight: 500;
transition: all 0.3s;
}
#controls button:hover {
background: #3b82f6;
transform: scale(1.05);
}
#controls button:active {
transform: scale(0.95);
}
#speed-slider {
-webkit-appearance: none;
width: 180px;
height: 4px;
background: #334155;
border-radius: 5px;
outline: none;
}
#speed-slider::-webkit-slider-thumb {
-webkit-appearance: none;
width: 18px;
height: 18px;
border-radius: 50%;
background: #2563eb;
cursor: pointer;
box-shadow: 0 0 10px rgba(37, 99, 235, 0.6);
}
#speed-display {
min-width: 60px;
text-align: center;
color: #94a3b8;
}
#reset-btn {
background: #475569 !important;
}
#reset-btn:hover {
background: #64748b !important;
}
#pause-btn.active {
background: #eab308 !important;
}
#hint {
position: absolute;
bottom: 90px;
left: 50%;
transform: translateX(-50%);
color: #64748b;
font-size: 13px;
letter-spacing: 0.5px;
pointer-events: none;
z-index: 10;
opacity: 0.6;
}
/* planet hover */
.planet-hover {
cursor: pointer;
}
@media (max-width: 768px) {
#controls {
bottom: 20px;
flex-wrap: wrap;
justify-content: center;
width: 90%;
padding: 10px 15px;
}
#speed-slider {
width: 120px;
}
#info {
font-size 12px;
padding: 8px 14px;
}
}
</style>
</head>
<body>
<div id="info">
<span class="label">Speed:</span> <span id="speed-val">1.00x</span> |
<span class="label">Time Scale:</span> <span id="time-scale">1.00</span>
<span style="margin-left: 15px; color: #475569;">🔭 Click a planet to focus</span>
</div>
<div id="controls">
<button id="pause">Pause</button>
<span style="color:#94a3b8;">Speed</span>
<input type="range" id="speed-slider" min="0" max="5" step="0.1" value="1">
<span id="speed-display">1.0x</span>
<button id="reset" class="reset-btn">Reset</button>
</div>
<div id="hint">Drag to rotate · Scroll to zoom</div>
<script type="importmap">
{
"imports": {
"three": "https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.module.js",
"three/addons/": "https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/"
}
}
</script>
<script type="module">
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
// ---------- SETUP ----------
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(55, window.innerWidth / window.innerHeight, 0.01, 2000);
camera.position.set(0, 30, 80);
camera.lookAt(0, 0, 0);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.2;
document.body.appendChild(renderer.domElement);
// Controls
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.08;
controls.minDistance = 5;
controls.maxDistance = 300;
controls.target.set(0, 0, 0);
// Lighting
const ambientLight = new THREE.AmbientLight(0x404060);
scene.add(ambientLight);
const sunLight = new THREE.PointLight(0xffdd99, 2.5, 150);
sunLight.position.set(0, 0, 0);
scene.add(sunLight);
// Extra dim fill light to see back of planets
const fillLight = new THREE.DirectionalLight(0x4466aa, 0.3);
fillLight.position.set(50, 50, 50);
scene.add(fillLight);
// ---------- STARFIELD ----------
// Shader for twinkling points
const starShaderMaterial = new THREE.ShaderMaterial({
uniforms: {
time: { value: 0 }
},
vertexShader: `
attribute float scale;
attribute float speed;
attribute float phase;
varying float vAlpha;
void main() {
vAlpha = 0.5 + 0.5 * sin(time * speed + phase);
vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
gl_PointSize = scale * (200.0 / -mvPosition.z);
gl_Position = projectionMatrix * mvPosition;
}
`,
fragmentShader: `
varying float vAlpha;
void main() {
float d = distance(gl_PointCoord, vec2(0.5, 0.5));
if (d > 0.5) discard;
gl_FragColor = vec4(vec3(1.0, 0.95, 0.8), vAlpha * (1.0 - d) * 0.9);
}
`,
transparent: true,
blending: THREE.AdditiveBlending,
depthWrite: false
});
const starCount = 6000;
const starGeo = new THREE.BufferGeometry();
const starPos = new Float32Array(starCount * 3);
const starScales = new Float32Array(starCount);
const starSpeeds = new Float32Array(starCount);
const starPhases = new Float32Array(starCount);
for (let i = 0; i < starCount; i++) {
// Random positions on a large sphere
const radius = 150 + Math.random() * 300;
const theta = Math.random() * Math.PI * 2;
const phi = Math.acos((Math.random() * 2) - 1);
starPos[i*3] = radius * Math.sin(phi) * Math.cos(theta);
starPos[i*3+1] = radius * Math.sin(phi) * Math.sin(theta);
starPos[i*3+2] = radius * Math.cos(phi);
starScales[i] = 0.8 + Math.random() * 2.0;
starSpeeds[i] = 0.5 + Math.random() * 2.0;
starPhases[i] = Math.random() * Math.PI * 2;
}
starGeo.setAttribute('position', new THREE.BufferAttribute(starPos, 3));
starGeo.setAttribute('scale', new THREE.BufferAttribute(starScales, 1));
starGeo.setAttribute('speed', new THREE.BufferAttribute(starSpeeds, 1));
starGeo.setAttribute('phase', new THREE.BufferAttribute(starPhases, 1));
const stars = new THREE.Points(starGeo, starShaderMaterial);
scene.add(stars);
// ---------- SUN ----------
const sunGeo = new THREE.SphereGeometry(4.5, 32, 32);
const sunMat = new THREE.MeshBasicMaterial({ color: 0xffaa33, emissive: 0xffaa33 });
const sun = new THREE.Mesh(sunGeo, sunMat);
scene.add(sun);
// Sun glow
const glowTexture = new THREE.CanvasTexture(createGlowTexture());
const glowMaterial = new THREE.SpriteMaterial({
map: glowTexture,
blending: THREE.AdditiveBlending,
transparent: true,
depthWrite: false
});
const glowSprite = new THREE.Sprite(glowMaterial);
glowSprite.scale.set(18, 18, 1);
sun.add(glowSprite);
function createGlowTexture() {
const canvas = document.createElement('canvas');
canvas.width = 256;
canvas.height = 256;
const ctx = canvas.getContext('2d');
const gradient = ctx.createRadialGradient(128, 128, 0, 128, 128, 128);
gradient.addColorStop(0, 'rgba(255, 180, 50, 1)');
gradient.addColorStop(0.3, 'rgba(255, 120, 30, 0.6)');
gradient.addColorStop(1, 'rgba(255, 80, 0, 0)');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, 256, 256);
return canvas;
}
// ---------- PLANET DATA ----------
// approximate distances (units) and sizes
const planetInfo = [
{ name: 'Mercury', distance: 6, radius: 0.5, color: 0x9a8a7a, period: 0.24 },
{ name: 'Venus', distance: 8, radius: 0.9, color: 0xe8b86e, period: 0.615 },
{ name: 'Earth', distance: 11, radius: 1.0, color: 0x4a6fa5, period: 1.0 },
{ name: 'Mars', distance: 14, radius: 0.6, color: 0xc1440e, period: 1.88 },
{ name: 'Jupiter', distance: 20, radius: 2.2, color: 0xc8b0a0, period: 11.86 },
{ name: 'Saturn', distance: 26, radius: 1.9, color: 0xe7d9a0, period: 29.45, hasRing: true },
{ name: 'Uranus', distance: 34, radius: 1.4, color: 0x76c6e8, period: 84.0 },
{ name: 'Neptune', distance: 42, radius: 1.3, color: 0x3f55c9, period: 164.8 }
];
// Create planets and orbit lines
const planets = [];
const labels = [];
const orbitLines = [];
// Base angular speed: Earth completes orbit in 60 seconds at 1x speed
const baseSpeed = (2 * Math.PI) / 60;
planetInfo.forEach((info) => {
// Orbital radius in world units
const r = info.distance;
// Create planet mesh
const geom = new THREE.SphereGeometry(info.radius, 32, 32);
const mat = new THREE.MeshStandardMaterial({
color: info.color,
roughness: 0.7,
metalness: 0.2
});
const mesh = new THREE.Mesh(geom, mat);
mesh.userData = info; // attach info for later
mesh.castShadow = false;
mesh.receiveShadow = false;
scene.add(mesh);
// Add Saturn's rings
if (info.hasRing) {
const ringGeo = new THREE.RingGeometry(info.radius * 1.4, info.radius * 2.0, 64);
const ringMat = new THREE.MeshBasicMaterial({
color: 0xd8cbb0,
side: THREE.DoubleSide,
transparent: true,
opacity: 0.7
});
const ringMesh = new THREE.Mesh(ringGeo, ringMat);
ringMesh.rotation.x = Math.PI / 2.2;
mesh.add(ringMesh);
}
// Orbit path (line)
const orbitPoints = [];
const segments = 128;
for (let i = 0; i <= segments; i++) {
const angle = (i / segments) * Math.PI * 2;
orbitPoints.push(new THREE.Vector3(Math.cos(angle) * r, 0, Math.sin(angle) * r));
}
const orbitGeo = new THREE.BufferGeometry().setFromPoints(orbitPoints);
const orbitMat = new THREE.LineBasicMaterial({
color: 0x3a4a6a,
transparent: true,
opacity: 0.35,
dashed: true,
dashSize: 0.8,
gapSize: 0.4
});
const orbitLine = new THREE.Line(orbitGeo, orbitMat);
orbitLine.computeLineDistances();
scene.add(orbitLine);
orbitLines.push(orbitLine);
// Create label sprite
const label = createLabelSprite(info.name);
// Position label initially, will update per frame
mesh.add(label);
label.position.set(0, info.radius * 1.2 + 0.8, 0);
labels.push(label);
// Initialize random angle
const angle = Math.random() * Math.PI * 2;
planets.push({
mesh,
info,
angle,
orbitalRadius: r,
angularSpeed: baseSpeed * (1 / info.period)
});
});
// Create a canvas texture for a label
function createLabelSprite(text) {
const canvas = document.createElement('canvas');
canvas.width = 128;
canvas.height = 64;
const ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Semi-transparent background
ctx.fillStyle = 'rgba(0,0,0,0.6)';
ctx.roundRect(20, 10, 88, 44, 12);
ctx.fill();
// Border
ctx.strokeStyle = 'rgba(100,150,255,0.4)';
ctx.lineWidth = 1;
ctx.roundRect(20, 10, 88, 44, 12);
ctx.stroke();
// Text
ctx.fillStyle = '#ffffff';
ctx.font = 'bold 20px "Segoe UI", Arial';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(text, 64, 32);
const texture = new THREE.CanvasTexture(canvas);
texture.minFilter = THREE.LinearFilter;
const material = new THREE.SpriteMaterial({
map: texture,
depthTest: false,
transparent: true
});
const sprite = new THREE.Sprite(material);
sprite.scale.set(2.5, 1.5, 1);
sprite.userData.hoverable = false;
return sprite;
}
// Helper: add roundRect method
CanvasRenderingContext2D.prototype.roundRect = function(x, y, w, h, r) {
if (w < 2 * r) r = w / 2;
if (h < 2 * r) r = h / 2;
this.beginPath();
this.moveTo(x + r, y);
this.arcTo(x + w, y, x + w, y + h, r);
this.arcTo(x + w, y + h, x, y + h, r);
this.arcTo(x, y + h, x, y, r);
this.arcTo(x, y, x + w, y, r);
this.closePath();
return this;
};
// ---------- SIMULATION STATE ----------
let isPaused = false;
let speedMultiplier = 1.0;
let simulationTime = 0;
// UI Elements
const pauseBtn = document.getElementById('pause');
const speedSlider = document.getElementById('speed-slider');
const speedDisplay = document.getElementById('speed-display');
const speedValue = document.getElementById('speed-value');
const timeScaleDisplay = document.getElementById('time-scale');
const resetBtn = document.getElementById('reset');
// ---------- RAYCASTING FOR CLICKING ----------
const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();
let focusedPlanet = null;
let focusLerp = 0;
let targetPos = null;
function onMouseClick(event) {
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
const meshes = planets.map(p => p.mesh);
const intersects = raycaster.intersectObjects(meshes, true);
if (intersects.length > 0) {
// Find top-level planet mesh (maybe from ring/child)
let obj = intersects[0].object;
while (obj.parent && !obj.userData.name) {
obj = obj.parent;
}
const planet = planets.find(p => p.mesh === obj);
if (planet) {
focusedPlanet = planet;
targetPos = planet.mesh.position.clone();
// Highlight
}
}
}
renderer.domElement.addEventListener('click', onMouseClick);
// ---------- KEYBOARD ----------
document.addEventListener('keydown', (e) => {
if (e.code === 'Space') {
e.preventDefault();
togglePause();
}
});
function togglePause() {
isPaused = !isPaused;
pauseBtn.textContent = isPaused ? 'Resume' : 'Pause';
pauseBtn.classList.toggle('active', isPaused);
}
// ---------- ANIMATION LOOP ----------
let elapsed = 0;
function animate() {
requestAnimationFrame(animate);
const delta = clock.getDelta();
if (!isPaused) {
simulationTime += delta * speedMultiplier;
elapsed = simulationTime;
}
// Update star shader time
starShaderMaterial.uniforms.time.value = elapsed;
// Update planet positions
planets.forEach(planet => {
const angle = planet.angularSpeed * simulationTime;
planet.mesh.position.x = Math.cos(angle) * planet.orbitalRadius;
planet.mesh.position.z = Math.sin(angle) * planet.orbitalRadius;
});
// Update labels to face camera? Not necessary for sprites, but they auto-face.
// Focus on planet if target set
if (targetPos) {
// Keep camera looking at planet, but object is moving
controls.target.lerp(targetPos, 0.05);
// Move camera to slight offset
const currentCamDir = camera.position.clone().sub(controls.target).normalize();
const desiredDist = 10 + focusedPlanet.info.radius * 2;
const newCamPos = controls.target.clone().add(currentCamDir.multiplyScalar(desiredDist));
camera.position.lerp(newCamPos, 0.05);
} else {
controls.target.set(0, 0, 0);
}
controls.update();
renderer.render(scene, camera);
}
const clock = new THREE.Clock();
// ---------- UI EVENT HANDLING ----------
speedSlider.addEventListener('input', (e) => {
speedMultiplier = parseFloat(e.target.value);
speedDisplay.textContent = speedMultiplier.toFixed(1) + 'x';
speedValue.textContent = speedMultiplier.toFixed(1) + 'x';
});
resetBtn.addEventListener('click', () => {
// Reset speed to 1
speedSlider.value = '1';
speedMultiplier = 1.0;
speedDisplay.textContent = '1.0x';
speedValue.textContent = '1.0x';
// Reset time
simulationTime = 0;
// Reset camera
camera.position.set(0, 30, 80);
controls.target.set(0, 0, 0);
focusedPlanet = null;
targetPos = null;
});
pauseBtn.addEventListener('click', togglePause);
// ---------- WINDOW RESIZE ----------
window.addEventListener('resize', onWindowResize);
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
// Start animation
animate();
// Set initial UI
speedDisplay.textContent = '1.0x';
speedValue.textContent = '1.0x';
</script>
</body>
</html>
Iniciá sesión para ver el prompt completo
Continuar con:
Al iniciar sesión, aceptás nuestros Términos de uso y Política de privacidad
Uso
Este prompt está diseñado para usarse con coding. Copiá el contenido de arriba y pegalo en tu herramienta de IA preferida.
Para mejores resultados, personalizá los marcadores (indicados con corchetes o mayúsculas) con tus requisitos específicos.
Referencias
- Categoría: Prompts de coding
- Fuente: https://x.com/eyishazyer/status/2075849990367273241
Discusión
0 comentarios