/** * Enhanced background - Space with floating geometric city silhouette shapes and */ class BackgroundShapes { constructor(scene) { this.scene = scene; this.shapes = []; this.stars = []; this.cityLights = []; this.frameCount = 0; this.createStars(); this.createCityscape(); this.createShapes(); } createStars() { // Starfield const starGeometry = new THREE.BufferGeometry(); const starCount = 500; const positions = new Float32Array(starCount * 3); const colors = new Float32Array(starCount * 3); const sizes = new Float32Array(starCount); for (let i = 0; i < starCount; i++) { positions[i * 3] = (Math.random() - 0.5) * 100; positions[i * 3 + 1] = Math.random() * 40 + 5; positions[i * 3 + 2] = -40 - Math.random() * 60; // Random star colors (white, cyan, magenta, yellow) const colorChoice = Math.random(); if (colorChoice < 0.7) { colors[i * 3] = 1; colors[i * 3 + 1] = 1; colors[i * 3 + 2] = 1; } else if (colorChoice < 0.85) { colors[i * 3] = 0; colors[i * 3 + 1] = 1; colors[i * 3 + 2] = 1; } else { colors[i * 3] = 1; colors[i * 3 + 1] = 0; colors[i * 3 + 2] = 1; } sizes[i] = Math.random() * 2 + 0.5; } starGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3)); starGeometry.setAttribute('color', new THREE.BufferAttribute(colors, 3)); starGeometry.setAttribute('size', new THREE.BufferAttribute(sizes, 1)); const starMaterial = new THREE.PointsMaterial({ size: 0.3, vertexColors: true, transparent: true, opacity: 0.8, sizeAttenuation: true }); this.starField = new THREE.Points(starGeometry, starMaterial); scene.add(this.starField); // Shooting stars this.shootingStars = []; } createCityscape() { // Create neon city silhouette in background const buildingCount = 30; const buildingMaterial = new THREE.MeshBasicMaterial({ color: 0x0a0515, transparent: true, opacity: 0.9 }); for (let i = 0; i < buildingCount; i++) { const width = 1 + Math.random() * 2; const height = 3 + Math.random() * 12; const depth = 0.5; const geometry = new THREE.BoxGeometry(width, height, depth); const building = new THREE.Mesh(geometry, buildingMaterial); building.position.set( -25 + i * 1.8 + Math.random() * 0.5, height / 2 - 1, -45 - Math.random() * 10 ); // Add window lights const windowRows = Math.floor(height / 1.5); const windowCols = Math.floor(width / 0.8); for (let row = 0; row < windowRows; row++) { for (let col = 0; col < windowCols; col++) { if (Math.random() > 0.4) { const windowGeom = new THREE.PlaneGeometry(0.3, 0.4); const windowColor = Math.random() > 0.5 ? 0x00ffff : 0xff00ff; const windowMat = new THREE.MeshBasicMaterial({ color: windowColor, transparent: true, opacity: 0.3 + Math.random() * 0.4 }); const windowMesh = new THREE.Mesh(windowGeom, windowMat); windowMesh.position.set( -width/2 + 0.4 + col * 0.7, -height/2 + 1 + row * 1.2, depth/2 + 0.01 ); building.add(windowMesh); } } } this.cityLights.push(building); scene.add(building); } // Ground reflection plane const reflectionGeom = new THREE.PlaneGeometry(60, 100); const reflectionMat = new THREE.MeshBasicMaterial({ color: 0x00ffff, transparent: true, opacity: 0.05, side: THREE.DoubleSide }); const reflection = new THREE.Mesh(reflectionGeom, reflectionMat); reflection.rotation.x = -Math.PI / 2; reflection.position.y = -0.48; reflection.position.z = -20; scene.add(reflection); } createShapes() { // Floating neon shapes const colors = [0x00ffff, 0xff00ff, 0x6644aa, 0xffd700]; const types = ['cube', 'octahedron', 'tetrahedron', 'torus']; for (let i = 0; i < 20; i++) { let geometry; const type = types[Math.floor(Math.random() * types.length)]; switch(type) { case 'cube': geometry = new THREE.BoxGeometry(0.8, 0.8, 0.8); break; case 'octahedron': geometry = new THREE.OctahedronGeometry(0.6); break; case 'tetrahedron': geometry = new THREE.TetrahedronGeometry(0.6); break; case 'torus': geometry = new THREE.TorusGeometry(0.4, 0.15, 8, 16); break; } const material = new THREE.MeshBasicMaterial({ color: colors[Math.floor(Math.random() * colors.length)], transparent: true, opacity: 0.2, wireframe: true }); const mesh = new THREE.Mesh(geometry, material); mesh.position.set( (Math.random() - 0.5) * 30, Math.random() * 12 + 3, -25 - Math.random() * 35 ); mesh.userData.rotationSpeed = { x: (Math.random() - 0.5) * 0.02, y: (Math.random() - 0.5) * 0.02, z: (Math.random() - 0.5) * 0.02 }; mesh.userData.floatSpeed = 0.003 + Math.random() * 0.008; mesh.userData.baseY = mesh.position.y; mesh.userData.floatAmount = 1 + Math.random() * 2; this.shapes.push(mesh); scene.add(mesh); } } update(frameCount) { this.frameCount = frameCount; // Animate starfield if (this.starField) { this.starField.rotation.y = frameCount * 0.0001; this.starField.position.z = Math.sin(frameCount * 0.001) * 2; } // Animate shapes this.shapes.forEach(mesh => { mesh.rotation.x += mesh.userData.rotationSpeed.x; mesh.rotation.y += mesh.userData.rotationSpeed.y; mesh.rotation.z += mesh.userData.rotationSpeed.z; mesh.position.y = mesh.userData.baseY + Math.sin(frameCount * mesh.userData.floatSpeed) * mesh.userData.floatAmount; }); // Twinkle city lights this.cityLights.forEach((building, idx) => { building.children.forEach((window, wIdx) => { if (window.material) { const twinkle = Math.sin(frameCount * 0.05 + idx * 0.5 + wIdx * 0.1); window.material.opacity = 0.3 + twinkle * 0.2; } }); }); // Random shooting star if (frameCount % 200 === 0 && Math.random() > 0.5) { this.createShootingStar(); } // Update shooting stars for (let i = this.shootingStars.length - 1; i >= 0; i--) { const ss = this.shootingStars[i]; ss.position.add(ss.userData.velocity); ss.material.opacity -= 0.02; if (ss.material.opacity <= 0) { this.scene.remove(ss); this.shootingStars.splice(i, 1); } } } createShootingStar() { const geometry = new THREE.BufferGeometry().setFromPoints([ new THREE.Vector3(0, 0, 0), new THREE.Vector3(0, -2, 1) ]); const material = new THREE.LineBasicMaterial({ color: 0xffffff, transparent: true, opacity: 1 }); const shootingStar = new THREE.Line(geometry, material); shootingStar.position.set( Math.random() * 40 - 20, 25 + Math.random() * 10, -30 - Math.random() * 20 ); shootingStar.userData.velocity = new THREE.Vector3(-0.3, -0.5, 0.5); this.shootingStars.push(shootingStar); this.scene.add(shootingStar); } }