Files
novafarma/EMERGENCY_SYSTEMS_RECOVERY/WindFoliageSystem.js
2026-01-16 02:43:46 +01:00

376 lines
12 KiB
JavaScript

/**
* WindFoliageSystem.js
*
* Dynamic Wind & Foliage System for Style 32 Dark-Chibi Noir
*
* Features:
* - Vertex Shader for hair (Kai, Ana, Gronk dreads)
* - Grass/foliage wave animation using Perlin Noise
* - Particle Emitter for falling leaves from trees
* - Biome-specific wind strength
* - Wobble physics for natural movement
*
* Performance: Uses shaders instead of thousands of sprites for RAM optimization
*/
export default class WindFoliageSystem {
constructor(scene) {
this.scene = scene;
// Global wind settings
this.wind = {
strength: 1.0, // Default wind strength (0.0 - 2.0)
direction: 0, // Wind direction in radians
speed: 0.5, // Wind animation speed
frequency: 2.0, // Perlin noise frequency
amplitude: 8.0 // Maximum displacement in pixels
};
// Biome-specific wind configurations
this.biomeWindSettings = {
'grassland': { strength: 1.0, frequency: 2.0 },
'mountains': { strength: 2.0, frequency: 3.5 },
'swamp': { strength: 0.3, frequency: 1.0 },
'forest': { strength: 0.8, frequency: 2.5 },
'desert': { strength: 1.5, frequency: 2.8 },
'tundra': { strength: 1.8, frequency: 3.0 },
'volcanic': { strength: 1.2, frequency: 2.0 }
};
// Particle systems for leaves
this.leafEmitters = [];
// Hair/foliage layers with wind effect
this.windAffectedLayers = [];
// Time accumulator for noise
this.time = 0;
}
/**
* Initialize the wind system
*/
init() {
console.log('🌬️ WindFoliageSystem: Initializing...');
// Create wind shader pipeline
this.createWindShader();
// Initialize leaf particle system
this.initLeafParticles();
console.log('✅ WindFoliageSystem: Ready!');
}
/**
* Create custom wind shader for vertex displacement
*/
createWindShader() {
// Vertex shader code for wind effect
const vertexShader = `
precision mediump float;
uniform mat4 uProjectionMatrix;
uniform mat4 uViewMatrix;
uniform float uTime;
uniform float uWindStrength;
uniform float uWindFrequency;
uniform float uWindAmplitude;
uniform vec2 uWindDirection;
attribute vec2 inPosition;
attribute vec2 inTexCoord;
varying vec2 vTexCoord;
// Perlin-like noise function (simplified for performance)
float noise(vec2 p) {
return fract(sin(dot(p, vec2(12.9898, 78.233))) * 43758.5453);
}
float smoothNoise(vec2 p) {
vec2 i = floor(p);
vec2 f = fract(p);
f = f * f * (3.0 - 2.0 * f); // Smoothstep
float a = noise(i);
float b = noise(i + vec2(1.0, 0.0));
float c = noise(i + vec2(0.0, 1.0));
float d = noise(i + vec2(1.0, 1.0));
return mix(mix(a, b, f.x), mix(c, d, f.x), f.y);
}
float perlinNoise(vec2 p) {
float total = 0.0;
float frequency = 1.0;
float amplitude = 1.0;
float maxValue = 0.0;
for(int i = 0; i < 4; i++) {
total += smoothNoise(p * frequency) * amplitude;
maxValue += amplitude;
amplitude *= 0.5;
frequency *= 2.0;
}
return total / maxValue;
}
void main() {
vTexCoord = inTexCoord;
// Apply wind displacement based on vertex height (Y position)
// Higher vertices (like hair tips) move more
float heightFactor = inTexCoord.y; // 0.0 at bottom, 1.0 at top
// Calculate Perlin noise for natural wind
vec2 noiseInput = inPosition * 0.01 * uWindFrequency + vec2(uTime * 0.3, uTime * 0.2);
float windOffset = perlinNoise(noiseInput);
// Apply wind displacement
vec2 displacement = uWindDirection * windOffset * uWindStrength * uWindAmplitude * heightFactor;
vec2 finalPosition = inPosition + displacement;
gl_Position = uProjectionMatrix * uViewMatrix * vec4(finalPosition, 0.0, 1.0);
}
`;
// Fragment shader (simple passthrough with texture)
const fragmentShader = `
precision mediump float;
uniform sampler2D uMainSampler;
varying vec2 vTexCoord;
void main() {
gl_FragColor = texture2D(uMainSampler, vTexCoord);
}
`;
// Store shader code for later use
this.windShader = {
vertex: vertexShader,
fragment: fragmentShader
};
console.log('🎨 Wind shader created');
}
/**
* Apply wind effect to a sprite layer (hair, grass, etc.)
* @param {Phaser.GameObjects.Sprite} sprite - The sprite to affect
* @param {string} layerType - Type: 'hair', 'grass', 'foliage'
*/
applyWindToSprite(sprite, layerType = 'grass') {
if (!sprite) return;
// Store reference
this.windAffectedLayers.push({
sprite: sprite,
type: layerType,
offsetX: 0,
offsetY: 0,
baseX: sprite.x,
baseY: sprite.y
});
console.log(`🌬️ Wind applied to ${layerType} layer`);
}
/**
* Initialize falling leaf particle system
*/
initLeafParticles() {
// Configuration for different leaf types
this.leafConfig = {
lifespan: 5000, // 5 seconds fall time
speed: { min: 20, max: 50 },
gravityY: 100,
rotate: { min: -180, max: 180 },
wobbleStrength: 30, // Horizontal wobble
colors: [0x8B4513, 0xA0522D, 0xD2691E, 0x654321] // Brown tones
};
console.log('🍃 Leaf particle system initialized');
}
/**
* Create leaf emitter for a tree
* @param {number} x - Tree X position
* @param {number} y - Tree Y position
* @param {number} emitRate - Leaves per second
*/
createLeafEmitter(x, y, emitRate = 0.5) {
const particles = this.scene.add.particles(x, y);
// Create simple leaf graphics (can be replaced with sprite)
const leafGraphics = this.scene.add.graphics();
leafGraphics.fillStyle(0x8B4513, 1);
leafGraphics.fillEllipse(0, 0, 8, 12);
leafGraphics.generateTexture('leaf', 8, 12);
leafGraphics.destroy();
const emitter = particles.createEmitter({
frame: 'leaf',
lifespan: this.leafConfig.lifespan,
speed: this.leafConfig.speed,
gravityY: this.leafConfig.gravityY,
rotate: this.leafConfig.rotate,
frequency: 1000 / emitRate, // Convert to milliseconds
scale: { start: 1, end: 0.8 },
alpha: { start: 1, end: 0.3 },
tint: Phaser.Utils.Array.GetRandom(this.leafConfig.colors)
});
this.leafEmitters.push({
particles: particles,
emitter: emitter,
x: x,
y: y
});
return emitter;
}
/**
* Set wind strength (affects all wind effects)
* @param {number} strength - Wind strength (0.0 - 2.0)
*/
setWindStrength(strength) {
this.wind.strength = Phaser.Math.Clamp(strength, 0, 2.0);
console.log(`🌬️ Wind strength set to: ${this.wind.strength.toFixed(2)}`);
}
/**
* Apply biome-specific wind settings
* @param {string} biomeName - Name of the biome
*/
setBiomeWind(biomeName) {
const settings = this.biomeWindSettings[biomeName.toLowerCase()];
if (settings) {
this.wind.strength = settings.strength;
this.wind.frequency = settings.frequency;
console.log(`🌍 Wind adjusted for ${biomeName}: strength=${settings.strength}, frequency=${settings.frequency}`);
} else {
console.warn(`⚠️ Unknown biome: ${biomeName}, using default wind`);
}
}
/**
* Perlin noise implementation (for CPU-side calculations)
*/
perlinNoise(x, y) {
const xi = Math.floor(x);
const yi = Math.floor(y);
const xf = x - xi;
const yf = y - yi;
// Simple hash function
const hash = (x, y) => {
const h = Math.sin(x * 12.9898 + y * 78.233) * 43758.5453;
return h - Math.floor(h);
};
const a = hash(xi, yi);
const b = hash(xi + 1, yi);
const c = hash(xi, yi + 1);
const d = hash(xi + 1, yi + 1);
// Smoothstep interpolation
const u = xf * xf * (3 - 2 * xf);
const v = yf * yf * (3 - 2 * yf);
return Phaser.Math.Linear(
Phaser.Math.Linear(a, b, u),
Phaser.Math.Linear(c, d, u),
v
);
}
/**
* Update wind effect (call every frame)
* @param {number} delta - Time since last frame in ms
*/
update(delta) {
this.time += delta * 0.001; // Convert to seconds
// Update all wind-affected sprites
this.windAffectedLayers.forEach(layer => {
const { sprite, type, baseX, baseY } = layer;
if (!sprite || !sprite.active) return;
// Calculate Perlin noise for smooth wind
const noiseX = baseX * 0.01 * this.wind.frequency + this.time * this.wind.speed;
const noiseY = baseY * 0.01 * this.wind.frequency + this.time * this.wind.speed * 0.7;
const windNoise = this.perlinNoise(noiseX, noiseY);
// Apply displacement
const displacement = windNoise * this.wind.strength * this.wind.amplitude;
// Different displacement for different layer types
if (type === 'hair') {
// Hair sways more at the tips
sprite.x = baseX + displacement * 1.5;
sprite.angle = displacement * 0.5; // Slight rotation
} else if (type === 'grass') {
// Grass bends from the base
sprite.x = baseX + displacement;
} else {
// Generic foliage
sprite.x = baseX + displacement * 0.8;
}
});
// Update leaf particles with wobble
this.leafEmitters.forEach(emitterData => {
if (emitterData.emitter) {
// Add horizontal wobble to falling leaves
const wobble = Math.sin(this.time * 2) * this.leafConfig.wobbleStrength * this.wind.strength;
emitterData.emitter.setSpeedX({ min: wobble - 10, max: wobble + 10 });
}
});
}
/**
* Debug: Show wind strength indicator
*/
showWindDebug() {
if (!this.windDebugText) {
this.windDebugText = this.scene.add.text(10, 10, '', {
font: '16px Arial',
fill: '#ffffff',
stroke: '#000000',
strokeThickness: 4
}).setScrollFactor(0).setDepth(10000);
}
this.windDebugText.setText([
`🌬️ Wind System`,
`Strength: ${this.wind.strength.toFixed(2)}`,
`Frequency: ${this.wind.frequency.toFixed(2)}`,
`Layers: ${this.windAffectedLayers.length}`,
`Emitters: ${this.leafEmitters.length}`
]);
}
/**
* Cleanup
*/
destroy() {
this.windAffectedLayers = [];
this.leafEmitters.forEach(e => {
if (e.particles) e.particles.destroy();
});
this.leafEmitters = [];
if (this.windDebugText) {
this.windDebugText.destroy();
}
}
}