// /src/three/scene.js import * as THREE from 'three'; export class MarsScene { constructor(containerEl, options = {}) { this.container = containerEl; this.width = containerEl.clientWidth; this.height = containerEl.clientHeight; this.scene = new THREE.Scene(); this.camera = new THREE.PerspectiveCamera(45, this.width / this.height, 0.1, 100); this.camera.position.set(0, 0, 2.5); this.renderer = new THREE.WebGLRenderer({ antialias: true }); this.renderer.setSize(this.width, this.height); this.container.appendChild(this.renderer.domElement); this._setupLights(); this._loadMarsTexture(options.marsUrl).then(texture => { this._makeMars(texture); }).catch(err => { console.error('Error cargando textura Marte', err); // fallback: color material const mat = new THREE.MeshStandardMaterial({ color: 0x884400 }); this._makeMars(mat); }); this._makeStarfield(1000); this.rotationSpeed = 0.01; // rad/s window.addEventListener('resize', () => this._onResize()); this.lastTime = performance.now(); this._animate(); } _setupLights() { const ambient = new THREE.AmbientLight(0xffffff, 0.5); this.scene.add(ambient); const dir = new THREE.DirectionalLight(0xffffff, 0.8); dir.position.set(5, 5, 5); this.scene.add(dir); } async _loadMarsTexture(url) { const loader = new THREE.TextureLoader(); return new Promise((resolve, reject) => { loader.load( url, tex => { tex.encoding = THREE.sRGBEncoding; resolve(tex); }, undefined, err => { reject(err); } ); }); } _makeMars(textureOrMaterial) { let mat; if (textureOrMaterial instanceof THREE.Texture) { mat = new THREE.MeshStandardMaterial({ map: textureOrMaterial }); } else { mat = textureOrMaterial; } const geo = new THREE.SphereGeometry(1, 64, 64); this.marsMesh = new THREE.Mesh(geo, mat); this.scene.add(this.marsMesh); } _makeStarfield(count) { const geom = new THREE.BufferGeometry(); const positions = new Float32Array(count * 3); for (let i = 0; i < count; i++) { const r = THREE.MathUtils.randFloat(5, 20); const theta = THREE.MathUtils.randFloatSpread(360); const phi = THREE.MathUtils.randFloatSpread(360); const x = r * Math.sin(theta) * Math.cos(phi); const y = r * Math.sin(theta) * Math.sin(phi); const z = r * Math.cos(theta); positions[3*i] = x; positions[3*i+1] = y; positions[3*i+2] = z; } geom.setAttribute('position', new THREE.BufferAttribute(positions, 3)); const mat = new THREE.PointsMaterial({ color: 0xffffff, size: 0.02 }); const stars = new THREE.Points(geom, mat); this.scene.add(stars); } _onResize() { this.width = this.container.clientWidth; this.height = this.container.clientHeight; this.camera.aspect = this.width / this.height; this.camera.updateProjectionMatrix(); this.renderer.setSize(this.width, this.height); } _animate() { requestAnimationFrame(() => this._animate()); const now = performance.now(); const dt = (now - this.lastTime) / 1000; this.lastTime = now; if (this.marsMesh) { this.marsMesh.rotation.y += this.rotationSpeed * dt; } this.renderer.render(this.scene, this camera); } // Exponer método para “limpiar” (quizás animar o cambiar material) showCleanState() { if (this.marsMesh && this.marsMesh.material) { // un shader o cambio leve: por ejemplo emitir luz o brillo this.marsMesh.material.emissive = new THREE.Color(0x003300); this.marsMesh.material.emissiveIntensity = 0.2; } } }