| <html> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>Molecular Fluid Mixing Simulation</title> |
| <style> |
| body { |
| background: black; |
| color: white; |
| display: flex; |
| flex-direction: column; |
| align-items: center; |
| } |
| .controls { |
| margin-top: 20px; |
| display: flex; |
| gap: 15px; |
| } |
| .slider-container { |
| display: flex; |
| flex-direction: column; |
| align-items: center; |
| } |
| </style> |
| </head> |
| <body> |
| <div class="container"> |
| <canvas id="canvas" width="800" height="600"></canvas> |
| <div class="controls"> |
| <button id="resetBtn">Reset Simulation</button> |
| <button id="pauseBtn">Pause</button> |
| <div class="slider-container"> |
| <label for="speedSlider">Molecular Speed</label> |
| <input type="range" id="speedSlider" min="0.1" max="3" step="0.1" value="1"> |
| </div> |
| <div class="slider-container"> |
| <label for="densitySlider">Molecule Density</label> |
| <input type="range" id="densitySlider" min="100" max="1000" step="50" value="400"> |
| </div> |
| <div class="slider-container"> |
| <label for="diffusionSlider">Diffusion Rate</label> |
| <input type="range" id="diffusionSlider" min="0.1" max="2" step="0.1" value="0.8"> |
| </div> |
| </div> |
| </div> |
| <script> |
| class Molecule { |
| constructor(x, y, type, canvas) { |
| this.x = x; |
| this.y = y; |
| this.vx = (Math.random() - 0.5) * 4; |
| this.vy = (Math.random() - 0.5) * 4; |
| this.type = type; // 'A' or 'B' |
| this.radius = type === 'A' ? 3 : 4; |
| this.mass = type === 'A' ? 1 : 1.5; |
| this.color = type === 'A' ? '#ff6b6b' : '#4ecdc4'; |
| this.canvas = canvas; |
| this.trail = []; |
| this.maxTrailLength = 10; |
| } |
| update(speedMultiplier, diffusionRate) { |
| // Add random brownian motion |
| this.vx += (Math.random() - 0.5) * 0.5 * diffusionRate; |
| this.vy += (Math.random() - 0.5) * 0.5 * diffusionRate; |
| // Apply friction |
| this.vx *= 0.98; |
| this.vy *= 0.98; |
| // Limit velocity |
| const maxVel = 3 * speedMultiplier; |
| const vel = Math.sqrt(this.vx * this.vx + this.vy * this.vy); |
| if (vel > maxVel) { |
| this.vx = (this.vx / vel) * maxVel; |
| this.vy = (this.vy / vel) * maxVel; |
| } |
| // Update position |
| this.x += this.vx * speedMultiplier; |
| this.y += this.vy * speedMultiplier; |
| // Boundary collision |
| if (this.x <= this.radius || this.x >= this.canvas.width - this.radius) { |
| this.vx *= -0.8; |
| this.x = Math.max(this.radius, Math.min(this.canvas.width - this.radius, this.x)); |
| } |
| if (this.y <= this.radius || this.y >= this.canvas.height - this.radius) { |
| this.vy *= -0.8; |
| this.y = Math.max(this.radius, Math.min(this.canvas.height - this.radius, this.y)); |
| } |
| // Update trail |
| this.trail.push({x: this.x, y: this.y}); |
| if (this.trail.length > this.maxTrailLength) { |
| this.trail.shift(); |
| } |
| } |
| draw(ctx) { |
| // Draw trail |
| ctx.strokeStyle = this.color; |
| ctx.lineWidth = 1; |
| ctx.globalAlpha = 0.3; |
| ctx.beginPath(); |
| for (let i = 1; i < this.trail.length; i++) { |
| ctx.moveTo(this.trail[i-1].x, this.trail[i-1].y); |
| ctx.lineTo(this.trail[i].x, this.trail[i].y); |
| } |
| ctx.stroke(); |
| // Draw molecule |
| ctx.globalAlpha = 0.8; |
| const gradient = ctx.createRadialGradient( |
| this.x - this.radius/3, this.y - this.radius/3, 0, |
| this.x, this.y, this.radius |
| ); |
| gradient.addColorStop(0, this.color); |
| gradient.addColorStop(1, this.color + '80'); |
| ctx.fillStyle = gradient; |
| ctx.beginPath(); |
| ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2); |
| ctx.fill(); |
| // Add shine effect |
| ctx.globalAlpha = 0.6; |
| ctx.fillStyle = 'white'; |
| ctx.beginPath(); |
| ctx.arc(this.x - this.radius/3, this.y - this.radius/3, this.radius/3, 0, Math.PI * 2); |
| ctx.fill(); |
| ctx.globalAlpha = 1; |
| } |
| checkCollision(other) { |
| const dx = this.x - other.x; |
| const dy = this.y - other.y; |
| const distance = Math.sqrt(dx * dx + dy * dy); |
| const minDistance = this.radius + other.radius; |
| if (distance < minDistance) { |
| // Collision response |
| const angle = Math.atan2(dy, dx); |
| const sin = Math.sin(angle); |
| const cos = Math.cos(angle); |
| // Rotate velocities |
| const vx1 = this.vx * cos + this.vy * sin; |
| const vy1 = this.vy * cos - this.vx * sin; |
| const vx2 = other.vx * cos + other.vy * sin; |
| const vy2 = other.vy * cos - other.vx * sin; |
| // Conservation of momentum |
| const finalVx1 = ((this.mass - other.mass) * vx1 + 2 * other.mass * vx2) / (this.mass + other.mass); |
| const finalVx2 = ((other.mass - this.mass) * vx2 + 2 * this.mass * vx1) / (this.mass + other.mass); |
| // Rotate velocities back |
| this.vx = finalVx1 * cos - vy1 * sin; |
| this.vy = vy1 * cos + finalVx1 * sin; |
| other.vx = finalVx2 * cos - vy2 * sin; |
| other.vy = vy2 * cos + finalVx2 * sin; |
| // Separate molecules |
| const overlap = minDistance - distance; |
| const separationX = (dx / distance) * overlap * 0.5; |
| const separationY = (dy / distance) * overlap * 0.5; |
| this.x += separationX; |
| this.y += separationY; |
| other.x -= separationX; |
| other.y -= separationY; |
| } |
| } |
| } |
| class FluidSimulation { |
| constructor(canvasId) { |
| this.canvas = document.getElementById(canvasId); |
| this.ctx = this.canvas.getContext('2d'); |
| this.molecules = []; |
| this.animationId = null; |
| this.isPaused = false; |
| this.speedMultiplier = 1; |
| this.diffusionRate = 0.8; |
| this.initMolecules(400); |
| this.bindEvents(); |
| this.animate(); |
| } |
| initMolecules(count) { |
| this.molecules = []; |
| const halfWidth = this.canvas.width / 2; |
| // Create fluid A molecules (left side) |
| for (let i = 0; i < count / 2; i++) { |
| const x = Math.random() * (halfWidth - 50) + 25; |
| const y = Math.random() * (this.canvas.height - 50) + 25; |
| this.molecules.push(new Molecule(x, y, 'A', this.canvas)); |
| } |
| // Create fluid B molecules (right side) |
| for (let i = 0; i < count / 2; i++) { |
| const x = Math.random() * (halfWidth - 50) + halfWidth + 25; |
| const y = Math.random() * (this.canvas.height - 50) + 25; |
| this.molecules.push(new Molecule(x, y, 'B', this.canvas)); |
| } |
| } |
| animate() { |
| if (!this.isPaused) { |
| this.update(); |
| this.draw(); |
| } |
| this.animationId = requestAnimationFrame(() => this.animate()); |
| } |
| update() { |
| // Update molecules |
| for (let molecule of this.molecules) { |
| molecule.update(this.speedMultiplier, this.diffusionRate); |
| } |
| // Check collisions |
| for (let i = 0; i < this.molecules.length; i++) { |
| for (let j = i + 1; j < this.molecules.length; j++) { |
| this.molecules[i].checkCollision(this.molecules[j]); |
| } |
| } |
| } |
| draw() { |
| // Clear canvas with fade effect |
| this.ctx.fillStyle = 'rgba(0, 0, 0, 0.1)'; |
| this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height); |
| // Draw dividing line (fades over time) |
| this.ctx.strokeStyle = 'rgba(255, 255, 255, 0.1)'; |
| this.ctx.lineWidth = 2; |
| this.ctx.setLineDash([5, 5]); |
| this.ctx.beginPath(); |
| this.ctx.moveTo(this.canvas.width / 2, 0); |
| this.ctx.lineTo(this.canvas.width / 2, this.canvas.height); |
| this.ctx.stroke(); |
| this.ctx.setLineDash([]); |
| // Draw molecules |
| for (let molecule of this.molecules) { |
| molecule.draw(this.ctx); |
| } |
| } |
| bindEvents() { |
| document.getElementById('resetBtn').addEventListener('click', () => { |
| this.initMolecules(parseInt(document.getElementById('densitySlider').value)); |
| }); |
| document.getElementById('pauseBtn').addEventListener('click', (e) => { |
| this.isPaused = !this.isPaused; |
| e.target.textContent = this.isPaused ? 'Resume' : 'Pause'; |
| }); |
| document.getElementById('speedSlider').addEventListener('input', (e) => { |
| this.speedMultiplier = parseFloat(e.target.value); |
| }); |
| document.getElementById('densitySlider').addEventListener('input', (e) => { |
| this.initMolecules(parseInt(e.target.value)); |
| }); |
| document.getElementById('diffusionSlider').addEventListener('input', (e) => { |
| this.diffusionRate = parseFloat(e.target.value); |
| }); |
| } |
| } |
| // Initialize simulation when page loads |
| window.addEventListener('load', () => { |
| new FluidSimulation('canvas'); |
| }); |
| </script> |
| </body> |
| </html> |