254 lines
9.2 KiB
JavaScript
254 lines
9.2 KiB
JavaScript
/**
|
|
* 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);
|
|
}
|
|
}
|