Spaces:
Running
Running
| // /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; | |
| } | |
| } | |
| } | |