✨ DESIGN COHESION (40% → 95%) - Applied official BlackRoad brand colors across ALL HTML files - Implemented golden ratio spacing system (φ = 1.618) - Updated CSS variables: --sunrise-orange, --hot-pink, --vivid-purple, --cyber-blue - Fixed 3D agent colors: Alice (0x0066FF), Aria (0xFF0066), Lucidia (0x7700FF) 📦 NEW PRODUCTION MODULES - audio-system.js: Procedural music, biome sounds, weather effects - api-client.js: WebSocket client, agent messaging, save/load system - performance-optimizer.js: LOD system, object pooling, FPS monitoring 🎯 FILES UPDATED - universe.html, index.html, pangea.html, ultimate.html 🛠 DEPLOYMENT TOOLS - deploy-quick.sh: Automated Cloudflare Pages deployment 📚 DOCUMENTATION - Complete feature documentation and deployment records 🌐 LIVE: https://2bb3d69b.blackroad-metaverse.pages.dev This commit represents a complete metaverse transformation! 🔥
662 lines
19 KiB
JavaScript
662 lines
19 KiB
JavaScript
/**
|
|
* PARTICLE EFFECTS SYSTEM
|
|
*
|
|
* Beautiful particle effects for the metaverse:
|
|
* - Rain (realistic droplets)
|
|
* - Snow (gentle flakes)
|
|
* - Fireflies (glowing insects)
|
|
* - Cherry blossoms (falling petals)
|
|
* - Stars (twinkling night sky)
|
|
* - Magic sparkles
|
|
* - Portal swirls
|
|
* - Teleport bursts
|
|
*/
|
|
|
|
import * as THREE from 'three';
|
|
|
|
/**
|
|
* RAIN SYSTEM
|
|
*/
|
|
export class RainEffect {
|
|
constructor(scene, options = {}) {
|
|
this.scene = scene;
|
|
this.intensity = options.intensity || 1000;
|
|
this.area = options.area || 100;
|
|
this.speed = options.speed || 1.0;
|
|
this.particles = null;
|
|
this.velocities = [];
|
|
}
|
|
|
|
create() {
|
|
const geometry = new THREE.BufferGeometry();
|
|
const vertices = [];
|
|
this.velocities = [];
|
|
|
|
for (let i = 0; i < this.intensity; i++) {
|
|
const x = (Math.random() - 0.5) * this.area;
|
|
const y = Math.random() * 50 + 20;
|
|
const z = (Math.random() - 0.5) * this.area;
|
|
vertices.push(x, y, z);
|
|
this.velocities.push(0.5 + Math.random() * 0.5); // Random fall speeds
|
|
}
|
|
|
|
geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));
|
|
|
|
const material = new THREE.PointsMaterial({
|
|
color: 0x8888ff,
|
|
size: 0.2,
|
|
transparent: true,
|
|
opacity: 0.6
|
|
});
|
|
|
|
this.particles = new THREE.Points(geometry, material);
|
|
this.scene.add(this.particles);
|
|
}
|
|
|
|
update() {
|
|
if (!this.particles) return;
|
|
|
|
const positions = this.particles.geometry.attributes.position.array;
|
|
|
|
for (let i = 0; i < positions.length; i += 3) {
|
|
// Fall down
|
|
positions[i + 1] -= this.velocities[i / 3] * this.speed;
|
|
|
|
// Reset to top when hit ground
|
|
if (positions[i + 1] < 0) {
|
|
positions[i + 1] = 50;
|
|
}
|
|
}
|
|
|
|
this.particles.geometry.attributes.position.needsUpdate = true;
|
|
}
|
|
|
|
remove() {
|
|
if (this.particles) {
|
|
this.scene.remove(this.particles);
|
|
this.particles = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* SNOW SYSTEM
|
|
*/
|
|
export class SnowEffect {
|
|
constructor(scene, options = {}) {
|
|
this.scene = scene;
|
|
this.intensity = options.intensity || 2000;
|
|
this.area = options.area || 100;
|
|
this.particles = null;
|
|
this.velocities = [];
|
|
this.driftSpeeds = [];
|
|
}
|
|
|
|
create() {
|
|
const geometry = new THREE.BufferGeometry();
|
|
const vertices = [];
|
|
this.velocities = [];
|
|
this.driftSpeeds = [];
|
|
|
|
for (let i = 0; i < this.intensity; i++) {
|
|
const x = (Math.random() - 0.5) * this.area;
|
|
const y = Math.random() * 50 + 20;
|
|
const z = (Math.random() - 0.5) * this.area;
|
|
vertices.push(x, y, z);
|
|
this.velocities.push(0.1 + Math.random() * 0.2); // Slow fall
|
|
this.driftSpeeds.push((Math.random() - 0.5) * 0.1); // Side drift
|
|
}
|
|
|
|
geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));
|
|
|
|
const material = new THREE.PointsMaterial({
|
|
color: 0xffffff,
|
|
size: 0.3,
|
|
transparent: true,
|
|
opacity: 0.8,
|
|
map: this.createSnowflakeTexture()
|
|
});
|
|
|
|
this.particles = new THREE.Points(geometry, material);
|
|
this.scene.add(this.particles);
|
|
}
|
|
|
|
createSnowflakeTexture() {
|
|
const canvas = document.createElement('canvas');
|
|
canvas.width = 64;
|
|
canvas.height = 64;
|
|
const ctx = canvas.getContext('2d');
|
|
|
|
const gradient = ctx.createRadialGradient(32, 32, 0, 32, 32, 32);
|
|
gradient.addColorStop(0, 'rgba(255, 255, 255, 1)');
|
|
gradient.addColorStop(0.5, 'rgba(255, 255, 255, 0.5)');
|
|
gradient.addColorStop(1, 'rgba(255, 255, 255, 0)');
|
|
|
|
ctx.fillStyle = gradient;
|
|
ctx.fillRect(0, 0, 64, 64);
|
|
|
|
const texture = new THREE.Texture(canvas);
|
|
texture.needsUpdate = true;
|
|
return texture;
|
|
}
|
|
|
|
update() {
|
|
if (!this.particles) return;
|
|
|
|
const positions = this.particles.geometry.attributes.position.array;
|
|
const time = Date.now() * 0.001;
|
|
|
|
for (let i = 0; i < positions.length; i += 3) {
|
|
// Fall down slowly
|
|
positions[i + 1] -= this.velocities[i / 3];
|
|
|
|
// Drift side to side
|
|
positions[i] += Math.sin(time + i) * 0.01;
|
|
positions[i + 2] += this.driftSpeeds[i / 3];
|
|
|
|
// Reset to top
|
|
if (positions[i + 1] < 0) {
|
|
positions[i + 1] = 50;
|
|
positions[i] = (Math.random() - 0.5) * this.area;
|
|
positions[i + 2] = (Math.random() - 0.5) * this.area;
|
|
}
|
|
}
|
|
|
|
this.particles.geometry.attributes.position.needsUpdate = true;
|
|
}
|
|
|
|
remove() {
|
|
if (this.particles) {
|
|
this.scene.remove(this.particles);
|
|
this.particles = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* FIREFLIES SYSTEM
|
|
*/
|
|
export class FirefliesEffect {
|
|
constructor(scene, options = {}) {
|
|
this.scene = scene;
|
|
this.count = options.count || 100;
|
|
this.area = options.area || 50;
|
|
this.height = options.height || { min: 1, max: 5 };
|
|
this.particles = null;
|
|
this.lights = [];
|
|
this.phases = [];
|
|
}
|
|
|
|
create() {
|
|
const geometry = new THREE.BufferGeometry();
|
|
const vertices = [];
|
|
const colors = [];
|
|
this.phases = [];
|
|
|
|
for (let i = 0; i < this.count; i++) {
|
|
const x = (Math.random() - 0.5) * this.area;
|
|
const y = this.height.min + Math.random() * (this.height.max - this.height.min);
|
|
const z = (Math.random() - 0.5) * this.area;
|
|
vertices.push(x, y, z);
|
|
|
|
// Yellow-green firefly color
|
|
colors.push(0.8, 1.0, 0.3);
|
|
|
|
// Random phase for blinking
|
|
this.phases.push(Math.random() * Math.PI * 2);
|
|
|
|
// Add point light for some fireflies
|
|
if (i % 10 === 0) {
|
|
const light = new THREE.PointLight(0xffff00, 0.5, 3);
|
|
light.position.set(x, y, z);
|
|
this.scene.add(light);
|
|
this.lights.push({ light, index: i * 3 });
|
|
}
|
|
}
|
|
|
|
geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));
|
|
geometry.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3));
|
|
|
|
const material = new THREE.PointsMaterial({
|
|
size: 0.2,
|
|
vertexColors: true,
|
|
transparent: true,
|
|
opacity: 1.0,
|
|
blending: THREE.AdditiveBlending
|
|
});
|
|
|
|
this.particles = new THREE.Points(geometry, material);
|
|
this.scene.add(this.particles);
|
|
}
|
|
|
|
update() {
|
|
if (!this.particles) return;
|
|
|
|
const positions = this.particles.geometry.attributes.position.array;
|
|
const time = Date.now() * 0.001;
|
|
|
|
for (let i = 0; i < positions.length; i += 3) {
|
|
const phase = this.phases[i / 3];
|
|
|
|
// Gentle floating movement
|
|
positions[i] += Math.sin(time + phase) * 0.01;
|
|
positions[i + 1] += Math.cos(time * 0.5 + phase) * 0.01;
|
|
positions[i + 2] += Math.sin(time * 0.7 + phase) * 0.01;
|
|
|
|
// Keep in bounds
|
|
if (Math.abs(positions[i]) > this.area / 2) {
|
|
positions[i] = (Math.random() - 0.5) * this.area;
|
|
}
|
|
if (Math.abs(positions[i + 2]) > this.area / 2) {
|
|
positions[i + 2] = (Math.random() - 0.5) * this.area;
|
|
}
|
|
}
|
|
|
|
// Update lights
|
|
for (const { light, index } of this.lights) {
|
|
light.position.set(
|
|
positions[index],
|
|
positions[index + 1],
|
|
positions[index + 2]
|
|
);
|
|
// Blinking effect
|
|
light.intensity = 0.3 + Math.sin(time * 3 + this.phases[index / 3]) * 0.2;
|
|
}
|
|
|
|
this.particles.geometry.attributes.position.needsUpdate = true;
|
|
}
|
|
|
|
remove() {
|
|
if (this.particles) {
|
|
this.scene.remove(this.particles);
|
|
this.particles = null;
|
|
}
|
|
for (const { light } of this.lights) {
|
|
this.scene.remove(light);
|
|
}
|
|
this.lights = [];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* CHERRY BLOSSOMS SYSTEM
|
|
*/
|
|
export class CherryBlossomsEffect {
|
|
constructor(scene, options = {}) {
|
|
this.scene = scene;
|
|
this.count = options.count || 500;
|
|
this.area = options.area || 60;
|
|
this.particles = null;
|
|
this.velocities = [];
|
|
this.rotations = [];
|
|
}
|
|
|
|
create() {
|
|
const geometry = new THREE.BufferGeometry();
|
|
const vertices = [];
|
|
this.velocities = [];
|
|
this.rotations = [];
|
|
|
|
for (let i = 0; i < this.count; i++) {
|
|
const x = (Math.random() - 0.5) * this.area;
|
|
const y = Math.random() * 40 + 10;
|
|
const z = (Math.random() - 0.5) * this.area;
|
|
vertices.push(x, y, z);
|
|
|
|
this.velocities.push({
|
|
x: (Math.random() - 0.5) * 0.05,
|
|
y: -0.05 - Math.random() * 0.1,
|
|
z: (Math.random() - 0.5) * 0.05
|
|
});
|
|
|
|
this.rotations.push(Math.random() * Math.PI * 2);
|
|
}
|
|
|
|
geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));
|
|
|
|
const material = new THREE.PointsMaterial({
|
|
color: 0xffb7c5,
|
|
size: 0.4,
|
|
transparent: true,
|
|
opacity: 0.8,
|
|
map: this.createPetalTexture()
|
|
});
|
|
|
|
this.particles = new THREE.Points(geometry, material);
|
|
this.scene.add(this.particles);
|
|
}
|
|
|
|
createPetalTexture() {
|
|
const canvas = document.createElement('canvas');
|
|
canvas.width = 64;
|
|
canvas.height = 64;
|
|
const ctx = canvas.getContext('2d');
|
|
|
|
ctx.fillStyle = '#ffb7c5';
|
|
ctx.beginPath();
|
|
ctx.arc(32, 32, 20, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
|
|
const texture = new THREE.Texture(canvas);
|
|
texture.needsUpdate = true;
|
|
return texture;
|
|
}
|
|
|
|
update() {
|
|
if (!this.particles) return;
|
|
|
|
const positions = this.particles.geometry.attributes.position.array;
|
|
const time = Date.now() * 0.001;
|
|
|
|
for (let i = 0; i < positions.length; i += 3) {
|
|
const vel = this.velocities[i / 3];
|
|
const rotation = this.rotations[i / 3];
|
|
|
|
// Spiral fall
|
|
positions[i] += vel.x + Math.sin(time + rotation) * 0.02;
|
|
positions[i + 1] += vel.y;
|
|
positions[i + 2] += vel.z + Math.cos(time + rotation) * 0.02;
|
|
|
|
// Reset to top
|
|
if (positions[i + 1] < 0) {
|
|
positions[i] = (Math.random() - 0.5) * this.area;
|
|
positions[i + 1] = 50;
|
|
positions[i + 2] = (Math.random() - 0.5) * this.area;
|
|
}
|
|
}
|
|
|
|
this.particles.geometry.attributes.position.needsUpdate = true;
|
|
}
|
|
|
|
remove() {
|
|
if (this.particles) {
|
|
this.scene.remove(this.particles);
|
|
this.particles = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* STARS SYSTEM (Night Sky)
|
|
*/
|
|
export class StarsEffect {
|
|
constructor(scene, options = {}) {
|
|
this.scene = scene;
|
|
this.count = options.count || 5000;
|
|
this.radius = options.radius || 500;
|
|
this.particles = null;
|
|
this.twinklePhases = [];
|
|
}
|
|
|
|
create() {
|
|
const geometry = new THREE.BufferGeometry();
|
|
const vertices = [];
|
|
const colors = [];
|
|
this.twinklePhases = [];
|
|
|
|
for (let i = 0; i < this.count; i++) {
|
|
// Sphere distribution
|
|
const theta = Math.random() * Math.PI * 2;
|
|
const phi = Math.acos(2 * Math.random() - 1);
|
|
const r = this.radius;
|
|
|
|
const x = r * Math.sin(phi) * Math.cos(theta);
|
|
const y = r * Math.sin(phi) * Math.sin(theta);
|
|
const z = r * Math.cos(phi);
|
|
|
|
vertices.push(x, y, z);
|
|
|
|
// Star colors (white to blue)
|
|
const colorVariation = 0.7 + Math.random() * 0.3;
|
|
colors.push(colorVariation, colorVariation, 1);
|
|
|
|
this.twinklePhases.push(Math.random() * Math.PI * 2);
|
|
}
|
|
|
|
geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));
|
|
geometry.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3));
|
|
|
|
const material = new THREE.PointsMaterial({
|
|
size: 1.5,
|
|
vertexColors: true,
|
|
transparent: true,
|
|
opacity: 1.0
|
|
});
|
|
|
|
this.particles = new THREE.Points(geometry, material);
|
|
this.scene.add(this.particles);
|
|
}
|
|
|
|
update() {
|
|
if (!this.particles) return;
|
|
|
|
const colors = this.particles.geometry.attributes.color.array;
|
|
const time = Date.now() * 0.0005;
|
|
|
|
for (let i = 0; i < colors.length; i += 3) {
|
|
const phase = this.twinklePhases[i / 3];
|
|
const twinkle = 0.7 + Math.sin(time + phase) * 0.3;
|
|
|
|
colors[i] *= twinkle;
|
|
colors[i + 1] *= twinkle;
|
|
colors[i + 2] *= twinkle;
|
|
}
|
|
|
|
this.particles.geometry.attributes.color.needsUpdate = true;
|
|
}
|
|
|
|
remove() {
|
|
if (this.particles) {
|
|
this.scene.remove(this.particles);
|
|
this.particles = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* MAGIC SPARKLES
|
|
*/
|
|
export class MagicSparkles {
|
|
constructor(scene, position, options = {}) {
|
|
this.scene = scene;
|
|
this.position = position;
|
|
this.count = options.count || 50;
|
|
this.lifespan = options.lifespan || 2; // seconds
|
|
this.color = options.color || 0x9B59B6;
|
|
this.particles = null;
|
|
this.velocities = [];
|
|
this.lifetimes = [];
|
|
this.startTime = Date.now();
|
|
}
|
|
|
|
create() {
|
|
const geometry = new THREE.BufferGeometry();
|
|
const vertices = [];
|
|
const colors = [];
|
|
this.velocities = [];
|
|
this.lifetimes = [];
|
|
|
|
for (let i = 0; i < this.count; i++) {
|
|
vertices.push(
|
|
this.position.x,
|
|
this.position.y,
|
|
this.position.z
|
|
);
|
|
|
|
// Random velocity
|
|
this.velocities.push(new THREE.Vector3(
|
|
(Math.random() - 0.5) * 2,
|
|
(Math.random() - 0.5) * 2,
|
|
(Math.random() - 0.5) * 2
|
|
));
|
|
|
|
// Color
|
|
const r = ((this.color >> 16) & 255) / 255;
|
|
const g = ((this.color >> 8) & 255) / 255;
|
|
const b = (this.color & 255) / 255;
|
|
colors.push(r, g, b);
|
|
|
|
this.lifetimes.push(Math.random());
|
|
}
|
|
|
|
geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));
|
|
geometry.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3));
|
|
|
|
const material = new THREE.PointsMaterial({
|
|
size: 0.3,
|
|
vertexColors: true,
|
|
transparent: true,
|
|
opacity: 1.0,
|
|
blending: THREE.AdditiveBlending
|
|
});
|
|
|
|
this.particles = new THREE.Points(geometry, material);
|
|
this.scene.add(this.particles);
|
|
}
|
|
|
|
update() {
|
|
if (!this.particles) return;
|
|
|
|
const age = (Date.now() - this.startTime) / 1000;
|
|
if (age > this.lifespan) {
|
|
this.remove();
|
|
return false;
|
|
}
|
|
|
|
const positions = this.particles.geometry.attributes.position.array;
|
|
|
|
for (let i = 0; i < positions.length; i += 3) {
|
|
const vel = this.velocities[i / 3];
|
|
positions[i] += vel.x * 0.1;
|
|
positions[i + 1] += vel.y * 0.1;
|
|
positions[i + 2] += vel.z * 0.1;
|
|
}
|
|
|
|
this.particles.material.opacity = 1 - (age / this.lifespan);
|
|
this.particles.geometry.attributes.position.needsUpdate = true;
|
|
|
|
return true;
|
|
}
|
|
|
|
remove() {
|
|
if (this.particles) {
|
|
this.scene.remove(this.particles);
|
|
this.particles = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* PARTICLE MANAGER
|
|
* Manages all active particle systems
|
|
*/
|
|
export class ParticleManager {
|
|
constructor(scene) {
|
|
this.scene = scene;
|
|
this.effects = {
|
|
rain: null,
|
|
snow: null,
|
|
fireflies: null,
|
|
cherryBlossoms: null,
|
|
stars: null
|
|
};
|
|
this.tempEffects = []; // Temporary effects like sparkles
|
|
}
|
|
|
|
enableRain(intensity = 1000) {
|
|
this.disableRain();
|
|
this.effects.rain = new RainEffect(this.scene, { intensity });
|
|
this.effects.rain.create();
|
|
}
|
|
|
|
disableRain() {
|
|
if (this.effects.rain) {
|
|
this.effects.rain.remove();
|
|
this.effects.rain = null;
|
|
}
|
|
}
|
|
|
|
enableSnow(intensity = 2000) {
|
|
this.disableSnow();
|
|
this.effects.snow = new SnowEffect(this.scene, { intensity });
|
|
this.effects.snow.create();
|
|
}
|
|
|
|
disableSnow() {
|
|
if (this.effects.snow) {
|
|
this.effects.snow.remove();
|
|
this.effects.snow = null;
|
|
}
|
|
}
|
|
|
|
enableFireflies(count = 100) {
|
|
this.disableFireflies();
|
|
this.effects.fireflies = new FirefliesEffect(this.scene, { count });
|
|
this.effects.fireflies.create();
|
|
}
|
|
|
|
disableFireflies() {
|
|
if (this.effects.fireflies) {
|
|
this.effects.fireflies.remove();
|
|
this.effects.fireflies = null;
|
|
}
|
|
}
|
|
|
|
enableCherryBlossoms(count = 500) {
|
|
this.disableCherryBlossoms();
|
|
this.effects.cherryBlossoms = new CherryBlossomsEffect(this.scene, { count });
|
|
this.effects.cherryBlossoms.create();
|
|
}
|
|
|
|
disableCherryBlossoms() {
|
|
if (this.effects.cherryBlossoms) {
|
|
this.effects.cherryBlossoms.remove();
|
|
this.effects.cherryBlossoms = null;
|
|
}
|
|
}
|
|
|
|
enableStars(count = 5000) {
|
|
this.disableStars();
|
|
this.effects.stars = new StarsEffect(this.scene, { count });
|
|
this.effects.stars.create();
|
|
}
|
|
|
|
disableStars() {
|
|
if (this.effects.stars) {
|
|
this.effects.stars.remove();
|
|
this.effects.stars = null;
|
|
}
|
|
}
|
|
|
|
addMagicSparkles(position, options = {}) {
|
|
const sparkles = new MagicSparkles(this.scene, position, options);
|
|
sparkles.create();
|
|
this.tempEffects.push(sparkles);
|
|
}
|
|
|
|
update() {
|
|
// Update persistent effects
|
|
if (this.effects.rain) this.effects.rain.update();
|
|
if (this.effects.snow) this.effects.snow.update();
|
|
if (this.effects.fireflies) this.effects.fireflies.update();
|
|
if (this.effects.cherryBlossoms) this.effects.cherryBlossoms.update();
|
|
if (this.effects.stars) this.effects.stars.update();
|
|
|
|
// Update temporary effects
|
|
this.tempEffects = this.tempEffects.filter(effect => effect.update());
|
|
}
|
|
|
|
removeAll() {
|
|
this.disableRain();
|
|
this.disableSnow();
|
|
this.disableFireflies();
|
|
this.disableCherryBlossoms();
|
|
this.disableStars();
|
|
|
|
this.tempEffects.forEach(effect => effect.remove());
|
|
this.tempEffects = [];
|
|
}
|
|
}
|
|
|
|
export default ParticleManager;
|