style.height = '0'; document.getElementById('waterJet').style.width = '0'; document.getElementById('waterJet').style.height = '0'; // Clear glass shards config.objects.glassShards = []; } // Main simulation loop function simulationLoop() { if (config.state.isBurst) { updateBurstPhysics(); } else { updateNormalPhysics(); } render(); requestAnimationFrame(simulationLoop); } // Update physics for normal state function updateNormalPhysics() { // Fish swimming config.objects.fish.forEach(fish => { if (!fish.isPanicked) { // Random swimming fish.vx += (Math.random() - 0.5) * config.physics.fishSwimStrength * fish.swimDirection; fish.vy += (Math.random() - 0.5) * config.physics.fishSwimStrength * 0.5; // Stay within bounds if (fish.x < 0) { fish.x = 0; fish.vx = Math.abs(fish.vx); fish.swimDirection = 1; } else if (fish.x > config.aquarium.width - 30) { fish.x = config.aquarium.width - 30; fish.vx = -Math.abs(fish.vx); fish.swimDirection = -1; } if (fish.y < 20) { fish.y = 20; fish.vy = Math.abs(fish.vy); } else if (fish.y > config.aquarium.maxWaterHeight - 15) { fish.y = config.aquarium.maxWaterHeight - 15; fish.vy = -Math.abs(fish.vy); } } }); } // Update physics for burst state function updateBurstPhysics() { const breachCenterY = config.crack.y + config.crack.height / 2; const waterAboveBreach = config.state.waterLevel - breachCenterY; // Water flow through the breach if (waterAboveBreach > 0) { const flowStrength = waterAboveBreach * config.physics.waterFlowMultiplier; config.state.waterLevel -= flowStrength * 0.1; // Create water jet particles if (Math.random() < 0.3) { const jetParticle = { x: config.aquarium.left + config.aquarium.width, y: config.aquarium.top + breachCenterY, vx: -5 - Math.random() * 5, vy: -2 + Math.random() * 4, size: 3 + Math.random() * 5, life: 10 + Math.random() * 20 }; config.state.jetParticles.push(jetParticle); } } // Update water jet particles for (let i = config.state.jetParticles.length - 1; i >= 0; i--) { const particle = config.state.jetParticles[i]; particle.x += particle.vx; particle.y += particle.vy; particle.vy += config.physics.gravity * 0.1; particle.life--; if (particle.life <= 0 || particle.y > config.aquarium.top + config.aquarium.height) { config.state.jetParticles.splice(i, 1); } } // Update puddle if (waterAboveBreach > 0) { config.state.puddleSize += config.physics.puddleSpreadRate; config.state.puddleX = config.aquarium.left + config.aquarium.width / 2; config.state.puddleY = config.aquarium.top + config.aquarium.height + 20; } // Update fish config.objects.fish.forEach(fish => { if (fish.isPanicked) { // Fish try to swim against the current fish.vx += (Math.random() - 0.5) * config.physics.fishPanickedSwimStrength; fish.vy += (Math.random() - 0.5) * config.physics.fishPanickedSwimStrength * 0.3; // If fish is near the breach, it gets swept out const distanceToBreach = Math.sqrt( Math.pow(fish.x - (config.aquarium.width - 10), 2) + Math.pow(fish.y - breachCenterY, 2) ); if (distanceToBreach < 50) { fish.vx -= 2 + Math.random() * 3; fish.vy += (Math.random() - 0.5) * 2; } } // Apply drag fish.vx *= config.physics.objectDrag; fish.vy *= config.physics.objectDrag; // Update position fish.x += fish.vx; fish.y += fish.vy; // Check if fish is out of the aquarium if (fish.x < -30 || fish.x > config.aquarium.width || fish.y < 0 || fish.y > config.aquarium.height) { // Fish is out of the aquarium, apply gravity fish.vy += config.physics.gravity; // Check for floor collision if (fish.y > config.aquarium.top + config.aquarium.height - 15) { fish.y = config.aquarium.top + config.aquarium.height - 15; fish.vy *= -0.5; fish.vx *= 0.8; } } }); // Update rocks (they just sink) config.objects.rocks.forEach(rock => { if (rock.y + rock.size < config.aquarium.top + config.state.waterLevel) { // Rock is underwater, apply buoyancy const submergedHeight = Math.min(rock.size, config.aquarium.top + config.state.waterLevel - rock.y); const buoyancy = (submergedHeight / rock.size) * (1 - rock.density / 2); rock.vy -= buoyancy * 0.1; } // Apply gravity rock.vy += config.physics.gravity * 0.1; // Apply drag rock.vx *= config.physics.objectDrag; rock.vy *= config.physics.objectDrag; // Update position rock.x += rock.vx; rock.y += rock.vy; // Check for floor collision if (rock.y + rock.size > config.aquarium.top + config.aquarium.height) { rock.y = config.aquarium.top + config.aquarium.height - rock.size; rock.vy *= -0.3; rock.vx *= 0.7; } }); // Update toys config.objects.toys.forEach(toy => { if (toy.y + 15 < config.aquarium.top + config.state.waterLevel) { // Toy is underwater, apply buoyancy const submergedHeight = Math.min(15, config.aquarium.top + config.state.waterLevel - toy.y); const buoyancy = (submergedHeight / 15) * (1 - toy.density); toy.vy -= buoyancy * 0.2; } // Apply gravity toy.vy += config.physics.gravity * 0.1; // Apply drag toy.vx *= config.physics.objectDrag; toy.vy *= config.physics.objectDrag; // Update position toy.x += toy.vx; toy.y += toy.vy; // Check if toy is near the breach const distanceToBreach = Math.sqrt( Math.pow(toy.x - (config.aquarium.width - 10), 2) + Math.pow(toy.y - breachCenterY, 2) ); if (distanceToBreach < 50) { toy.vx -= 1 + Math.random() * 2; toy.vy += (Math.random() - 0.5) * 1; } // Check for floor collision if (toy.y + 15 > config.aquarium.top + config.aquarium.height) { toy.y = config.aquarium.top + config.aquarium.height - 15; toy.vy *= -0.5; toy.vx *= 0.8; } }); // Update glass shards config.objects.glassShards.forEach(shard => { // Apply gravity shard.vy += config.physics.gravity * 0.2; // Apply water drag if underwater if (shard.y + shard.height > config.aquarium.top + config.state.waterLevel) { shard.vx *= config.physics.waterDrag; shard.vy *= config.physics.waterDrag; } // Update position shard.x += shard.vx; shard.y += shard.vy; // Update rotation shard.rotation += shard.angularVelocity; // Check for floor collision if (shard.y + shard.height > config.aquarium.top + config.aquarium.height) { shard.y = config.aquarium.top + config.aquarium.height - shard.height; shard.vy *= -0.4; shard.vx *= 0.8; shard.angularVelocity *= 0.8; } }); } // Render the simulation function render() { // Update water line document.getElementById('waterLine').style.top = `${config.aquarium.height - config.state.waterLevel}px`; // Render fish config.objects.fish.forEach(fish => { fish.element.style.left = `${fish.x}px`; fish.element.style.top = `${fish.y}px`; // Change color if panicked if (fish.isPanicked) { fish.element.style.backgroundColor = '#ff0000'; } else { fish.element.style.backgroundColor = '#ff5555'; } }); // Render rocks config.objects.rocks.forEach(rock => { rock.element.style.left = `${rock.x}px`; rock.element.style.top = `${rock.y}px`; }); // Render plants (static) // Plants don't move in this simulation // Render toys config.objects.toys.forEach(toy => { toy.element.style.left = `${toy.x}px`; toy.element.style.top = `${toy.y}px`; }); // Render glass shards config.objects.glassShards.forEach(shard => { shard.element.style.left = `${shard.x}px`; shard.element.style.top = `${shard.y}px`; shard.element.style.transform = `rotate(${shard.rotation}rad)`; }); // Render water jet particles const waterJet = document.getElementById('waterJet'); if (config.state.jetParticles.length > 0) { const particle = config.state.jetParticles[0]; waterJet.style.left = `${particle.x}px`; waterJet.style.top = `${particle.y}px`; waterJet.style.width = `${particle.size}px`; waterJet.style.height = `${particle.size}px`; } else { waterJet.style.width = '0'; waterJet.style.height = '0'; } // Render puddle const puddle = document.getElementById('puddle'); if (config.state.puddleSize > 0) { puddle.style.left = `${config.state.puddleX - config.state.puddleSize / 2}px`; puddle.style.top = `${config.state.puddleY - config.state.puddleSize / 4}px`; puddle.style.width = `${config.state.puddleSize}px`; puddle.style.height = `${config.state.puddleSize / 2}px`; } } // Start the simulation init();