it is now settings.py settings handles loading the json file for the particles and imports, and potentially other things such as actual settings that are saveable and more
605 lines
26 KiB
Python
605 lines
26 KiB
Python
#File Name: sim.py
|
|
|
|
#Load the imports. Pygame is what makes this even work and so simple may consider other engines for performance depends on learning curve.
|
|
|
|
from settings import json, random, time, particle_properties
|
|
|
|
# Load particle properties from json so we know what particles we got and how they should be simulated.
|
|
class Particle:
|
|
def __init__(self, position, velocity, mass, particle_type, properties, temperature=20):
|
|
self.position = position # (x, y)
|
|
self.velocity = velocity # (vx, vy)
|
|
self.mass = mass
|
|
self.particle_type = particle_type
|
|
|
|
# Core properties
|
|
self.size = properties.get("size", 1)
|
|
self.hardness = properties.get("hardness", 0.5)
|
|
self.color = properties.get("color", [255, 255, 255, 255])
|
|
self.temperature = properties.get("temperature", temperature)
|
|
|
|
# Physics properties
|
|
self.conductivity = properties.get("conductivity", 0)
|
|
self.heat_capacity = properties.get("heat_capacity", 1)
|
|
self.flamability = properties.get("flamability", 0.0)
|
|
self.friction = properties.get("friction", 0.5)
|
|
self.viscosity = properties.get("viscosity", 1.0)
|
|
self.pressure = properties.get("pressure", 0)
|
|
|
|
# State properties
|
|
self.liquid = properties.get("liquid", False)
|
|
self.solid = properties.get("solid", True)
|
|
self.is_gas = properties.get("is_gas", False)
|
|
|
|
# Temperature transition properties
|
|
self.melt = properties.get("melt", None)
|
|
self.melt_temperature = properties.get("melt_temperature", None)
|
|
self.solidify = properties.get("solidify", None)
|
|
self.solidify_temperature = properties.get("solidify_temperature", None)
|
|
self.evaporate = properties.get("evaporate", None)
|
|
self.evaporate_temperature = properties.get("evaporate_temperature", None)
|
|
self.freeze = properties.get("freeze", None)
|
|
self.freeze_temperature = properties.get("freeze_temperature", None)
|
|
|
|
# Special properties
|
|
self.explosive = properties.get("explosive", False)
|
|
self.explosion_radius = properties.get("explosion_radius", 0)
|
|
self.explosion_color = properties.get("explosion_color", [0, 0, 0])
|
|
|
|
@classmethod
|
|
def from_type(cls, position, particle_type, properties):
|
|
default_velocity = [0, 0]
|
|
default_mass = properties.get("mass", 1.0)
|
|
return cls(position, default_velocity, default_mass, particle_type, properties)
|
|
|
|
|
|
class Simulation:
|
|
# the main class of the simulation.
|
|
|
|
def __init__(self, width, height, x=0, y=0):
|
|
self.x = x
|
|
self.y = y
|
|
self.new_x = 0
|
|
self.new_y = 0
|
|
self.width = width
|
|
self.height = height
|
|
self.particle_size = 3
|
|
self.particles = [[None for _ in range(height)] for _ in range(width)]
|
|
self.active_particles = set()
|
|
self.cell_size = 32
|
|
self.spatial_grid = {}
|
|
self.brush_size = 1
|
|
self.max_brush_size = 20
|
|
self.particle_properties = particle_properties
|
|
self.current_particle_type = 'sand'
|
|
self.gravity = 9.8 # m/s^2, adjustable based on the scale of simulation
|
|
self.wind = [0.0, 0.0] # Global wind vector (x, y)
|
|
|
|
|
|
def handle_phase_transitions(self, particle, x, y):
|
|
"""Handle all phase transitions for a particle"""
|
|
# Check evaporation
|
|
if hasattr(particle, 'evaporate_temperature') and particle.evaporate_temperature is not None:
|
|
if particle.temperature >= particle.evaporate_temperature and particle.evaporate:
|
|
self.transform_particle(x, y, particle.evaporate)
|
|
|
|
# Check melting
|
|
if hasattr(particle, 'melt_temperature') and particle.melt_temperature is not None:
|
|
if particle.temperature >= particle.melt_temperature and particle.melt:
|
|
self.transform_particle(x, y, particle.melt)
|
|
|
|
# Check freezing
|
|
if hasattr(particle, 'freeze_temperature') and particle.freeze_temperature is not None:
|
|
if particle.temperature <= particle.freeze_temperature and particle.freeze:
|
|
self.transform_particle(x, y, particle.freeze)
|
|
|
|
# Check solidification
|
|
if hasattr(particle, 'solidify_temperature') and particle.solidify_temperature is not None:
|
|
if particle.temperature <= particle.solidify_temperature and particle.solidify:
|
|
self.transform_particle(x, y, particle.solidify)
|
|
|
|
|
|
def handle_particle_interactions(self, dt):
|
|
"""Handle interactions between different particle types"""
|
|
for x, y in list(self.active_particles):
|
|
particle = self.particles[x][y]
|
|
if not particle:
|
|
continue
|
|
|
|
# Check neighboring particles
|
|
for dx, dy in [(-1, 0), (1, 0), (0, -1), (0, 1), (-1, -1), (1, -1), (-1, 1), (1, 1)]:
|
|
nx, ny = x + dx, y + dy
|
|
if 0 <= nx < self.width and 0 <= ny < self.height:
|
|
neighbor = self.particles[nx][ny]
|
|
if neighbor:
|
|
self.process_interaction(particle, neighbor, x, y, nx, ny)
|
|
|
|
|
|
def process_interaction(self, particle1, particle2, x1, y1, x2, y2):
|
|
"""Process specific interactions between two particles"""
|
|
# Water + Sand = Mud
|
|
if (particle1.particle_type == 'water' and particle2.particle_type == 'sand' or
|
|
particle2.particle_type == 'water' and particle1.particle_type == 'sand'):
|
|
self.create_mud(x1, y1)
|
|
self.particles[x2][y2] = None
|
|
self.active_particles.discard((x2, y2))
|
|
|
|
# Lava/Fire effects
|
|
if particle1.particle_type in ['lava', 'fire', 'flame'] or particle2.particle_type in ['lava', 'fire', 'flame']:
|
|
target = particle2 if particle1.particle_type in ['lava', 'fire', 'flame'] else particle1
|
|
target_x, target_y = (x2, y2) if particle1.particle_type in ['lava', 'fire', 'flame'] else (x1, y1)
|
|
|
|
# Water to Steam
|
|
if target.particle_type == 'water':
|
|
self.transform_particle(target_x, target_y, 'steam')
|
|
|
|
# Wood to Fire
|
|
elif target.particle_type == 'wood':
|
|
if random.random() < 0.3: # 30% chance to ignite
|
|
self.transform_particle(target_x, target_y, 'fire')
|
|
|
|
|
|
def create_mud(self, x, y):
|
|
"""Create mud particle from water and sand interaction"""
|
|
if 'mud' in self.particle_properties:
|
|
properties = self.particle_properties['mud']
|
|
new_particle = Particle.from_type((x, y), 'mud', properties)
|
|
self.particles[x][y] = new_particle
|
|
self.active_particles.add((x, y))
|
|
|
|
|
|
def transform_particle(self, x, y, new_type):
|
|
"""Transform a particle into a different type"""
|
|
if new_type in self.particle_properties:
|
|
properties = self.particle_properties[new_type]
|
|
new_particle = Particle.from_type((x, y), new_type, properties)
|
|
self.particles[x][y] = new_particle
|
|
self.active_particles.add((x, y))
|
|
|
|
|
|
def handle_gas_movement(self, particle, x, y):
|
|
"""Handle gas particle movement"""
|
|
if particle.is_gas:
|
|
dx = random.uniform(-1, 1)
|
|
dy = random.uniform(-2, 0) # Bias upward movement
|
|
new_x = int(x + dx)
|
|
new_y = int(y + dy)
|
|
|
|
if 0 <= new_x < self.width and 0 <= new_y < self.height:
|
|
if self.particles[new_x][new_y] is None:
|
|
self.particles[x][y] = None
|
|
self.particles[new_x][new_y] = particle
|
|
self.active_particles.add((new_x, new_y))
|
|
self.active_particles.discard((x, y))
|
|
|
|
|
|
def temperature(self, dt):
|
|
"""Handle temperature changes and state transitions"""
|
|
for x, y in list(self.active_particles):
|
|
particle = self.particles[x][y]
|
|
if not particle:
|
|
continue
|
|
if particle.temperature > 1100:
|
|
# Transition to gas
|
|
particle.is_gas = True
|
|
particle.temperature = 1100
|
|
particle.velocity = [random.uniform(-1, 1), random.uniform(-1, 1)]
|
|
particle.temperature < 1100
|
|
particle.is_gas = False
|
|
|
|
|
|
def calculate_forces(self, particle, x, y):
|
|
"""Calculate net forces acting on a particle."""
|
|
fx, fy = 0.0, 0.0 # Initialize forces
|
|
|
|
# Apply wind force
|
|
fx += self.wind[0] * (1.0 if not particle.is_gas else 0.5)
|
|
fy += self.wind[1] * (1.0 if not particle.is_gas else 0.5)
|
|
|
|
# Apply drag force
|
|
drag = particle.viscosity * -1
|
|
fx += drag * particle.velocity[0]
|
|
fy += drag * particle.velocity[1]
|
|
|
|
# Check neighboring particles
|
|
for dx, dy in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
|
|
nx, ny = x + dx, y + dy
|
|
if 0 <= nx < self.width and 0 <= ny < self.height:
|
|
neighbor = self.particles[nx][ny]
|
|
if neighbor:
|
|
# Temperature effects
|
|
if hasattr(neighbor, 'temperature') and hasattr(particle, 'temperature'):
|
|
if neighbor.temperature > particle.temperature:
|
|
fy += (neighbor.temperature - particle.temperature) * 0.1
|
|
# Gas pressure effects
|
|
if hasattr(neighbor, 'is_gas') and hasattr(particle, 'is_gas'):
|
|
if neighbor.is_gas and not particle.is_gas:
|
|
fx += dx * 0.1
|
|
fy += dy * 0.1
|
|
|
|
return fx, fy
|
|
|
|
|
|
def ignite_particle(self, particle):
|
|
"""Handle ignition and burning of flammable particles."""
|
|
if hasattr(particle, 'flamability') and particle.flamability > 0.5:
|
|
if hasattr(particle, 'temperature') and particle.temperature > 150:
|
|
particle.type = 'fire'
|
|
particle.temperature += 200
|
|
# Add burning effect for wood
|
|
if particle.type == 'wood':
|
|
particle.burning = True
|
|
particle.burn_time = 100 # Adjust burn time as needed
|
|
|
|
|
|
def spread_fire(self):
|
|
"""Spread fire to neighboring particles."""
|
|
for x, y in list(self.active_particles):
|
|
particle = self.particles[x][y]
|
|
if particle and (particle.particle_type == 'fire' or getattr(particle, 'burning', False)):
|
|
# Check all neighboring cells including diagonals
|
|
for dx in [-1, 0, 1]:
|
|
for dy in [-1, 0, 1]:
|
|
nx, ny = x + dx, y + dy
|
|
if 0 <= nx < self.width and 0 <= ny < self.height:
|
|
neighbor = self.particles[nx][ny]
|
|
if neighbor and hasattr(neighbor, 'flamability'):
|
|
if neighbor.particle_type == 'wood':
|
|
# Higher chance to ignite wood
|
|
if random.random() < 0.3: # 30% chance to spread
|
|
self.ignite_particle(neighbor)
|
|
elif neighbor.flamability > 0:
|
|
if random.random() < 0.1: # 10% chance for other materials
|
|
self.ignite_particle(neighbor)
|
|
|
|
|
|
def handle_temperature(self, dt):
|
|
"""Handle temperature changes and state transitions"""
|
|
for x, y in list(self.active_particles):
|
|
particle = self.particles[x][y]
|
|
if not particle:
|
|
continue
|
|
|
|
# Check for melting with proper attribute validation
|
|
if (hasattr(particle, 'melt') and hasattr(particle, 'melt_temperature')
|
|
and particle.melt_temperature is not None):
|
|
if particle.temperature >= particle.melt_temperature:
|
|
new_type = particle.melt
|
|
if new_type in self.particle_properties:
|
|
self.transform_particle(x, y, new_type)
|
|
|
|
# Check for solidification with proper attribute validation
|
|
if (hasattr(particle, 'solidify') and hasattr(particle, 'solidify_temperature')
|
|
and particle.solidify_temperature is not None):
|
|
if particle.temperature <= particle.solidify_temperature:
|
|
new_type = particle.solidify
|
|
if new_type in self.particle_properties:
|
|
self.transform_particle(x, y, new_type)
|
|
|
|
# Temperature spread to neighbors
|
|
for dx, dy in [(-1, 0), (1, 0), (0, -1), (0, 1)]:
|
|
nx, ny = x + dx, y + dy
|
|
if 0 <= nx < self.width and 0 <= ny < self.height:
|
|
neighbor = self.particles[nx][ny]
|
|
if (neighbor and hasattr(neighbor, 'temperature')
|
|
and neighbor.temperature is not None):
|
|
temp_diff = particle.temperature - neighbor.temperature
|
|
heat_transfer = temp_diff * 0.1 * dt
|
|
particle.temperature -= heat_transfer
|
|
neighbor.temperature += heat_transfer
|
|
|
|
|
|
def burning(self):
|
|
"""Handle burning of particles."""
|
|
for x, y in list(self.active_particles):
|
|
particle = self.particles[x][y]
|
|
if particle and hasattr(particle, 'burning') and particle.burning:
|
|
particle.temperature += 10
|
|
if particle.temperature > 1000:
|
|
self.particles[x][y] = None
|
|
self.active_particles.remove((x, y))
|
|
self.spatial_grid.pop((x, y), None)
|
|
|
|
|
|
def get_cell_key(self, x, y):
|
|
# Convert coordinates to grid cell
|
|
cell_x = x // self.cell_size
|
|
cell_y = y // self.cell_size
|
|
return (cell_x, cell_y)
|
|
|
|
|
|
def add_to_spatial_grid(self, particle, x, y):
|
|
cell_key = self.get_cell_key(x, y)
|
|
if cell_key not in self.spatial_grid:
|
|
self.spatial_grid[cell_key] = set()
|
|
self.spatial_grid[cell_key].add((x, y))
|
|
|
|
|
|
def remove_from_spatial_grid(self, x, y):
|
|
cell_key = self.get_cell_key(x, y)
|
|
if cell_key in self.spatial_grid:
|
|
self.spatial_grid[cell_key].discard((x, y))
|
|
|
|
|
|
def create_particle_circle(self, center_x, center_y):
|
|
brush_size = int(self.brush_size)
|
|
for dx in range(-brush_size, brush_size + 1):
|
|
for dy in range(-brush_size, brush_size + 1):
|
|
if dx*dx + dy*dy <= brush_size*brush_size: # Circle check
|
|
self.create_particle(center_x + dx * self.particle_size,
|
|
center_y + dy * self.particle_size)
|
|
|
|
|
|
def create_particle(self, x, y):
|
|
"""Create a new particle with full property support"""
|
|
particle_type = self.current_particle_type.lower()
|
|
if particle_type in self.particle_properties:
|
|
grid_x = x // self.particle_size
|
|
grid_y = y // self.particle_size
|
|
if 0 <= grid_x < self.width and 0 <= grid_y < self.height:
|
|
properties = self.particle_properties[particle_type]
|
|
position = (grid_x, grid_y)
|
|
new_particle = Particle(
|
|
position=position,
|
|
velocity=[0, 0],
|
|
mass=properties.get('mass', 1.0),
|
|
particle_type=particle_type,
|
|
properties=properties
|
|
)
|
|
|
|
if 0 <= grid_x < len(self.particles) and 0 <= grid_y < len(self.particles[0]):
|
|
self.particles[grid_x][grid_y] = new_particle
|
|
self.active_particles.add((grid_x, grid_y))
|
|
|
|
|
|
def update_spatial_grid(self):
|
|
"""Update spatial grid for optimized collision detection"""
|
|
self.spatial_grid.clear()
|
|
for x, y in self.active_particles:
|
|
self.add_to_spatial_grid(self.particles[x][y], x, y)
|
|
|
|
|
|
def apply_gravity(self, dt):
|
|
"""Handle only gravity and basic particle movement"""
|
|
self.spatial_grid.clear()
|
|
|
|
for x, y in list(self.active_particles):
|
|
particle = self.particles[x][y]
|
|
if not particle or particle.particle_type == 'wall':
|
|
continue
|
|
|
|
# Apply gravity
|
|
new_y = y + 1
|
|
new_x = x
|
|
|
|
# Check boundaries
|
|
if not (0 <= new_x < self.width and 0 <= new_y < self.height):
|
|
continue
|
|
|
|
# Handle granular materials (sand, dirt)
|
|
if particle.particle_type in ['sand', 'dirt']:
|
|
if self.particles[x][new_y] is None:
|
|
new_x, new_y = x, y + 1
|
|
else:
|
|
# Try diagonal movement with randomization
|
|
diagonal_dirs = [(-1, 1), (1, 1)]
|
|
random.shuffle(diagonal_dirs)
|
|
for dx, dy in diagonal_dirs:
|
|
test_x = x + dx
|
|
test_y = y + dy
|
|
if (0 <= test_x < self.width and 0 <= test_y < self.height and
|
|
self.particles[test_x][test_y] is None):
|
|
if random.random() < 0.8: # 80% chance to move diagonally
|
|
new_x = test_x
|
|
new_y = test_y
|
|
break
|
|
|
|
# Handle liquid movement (water, lava)
|
|
elif particle.liquid:
|
|
if self.particles[x][new_y] is None:
|
|
new_x = x
|
|
new_y = y + 1
|
|
else:
|
|
spread_directions = [(-1, 0), (1, 0)]
|
|
random.shuffle(spread_directions)
|
|
for dx, _ in spread_directions:
|
|
test_x = x + dx
|
|
if (0 <= test_x < self.width and
|
|
self.particles[test_x][y] is None):
|
|
new_x = test_x
|
|
new_y = y
|
|
break
|
|
|
|
# Move particle if destination is empty
|
|
if self.particles[new_x][new_y] is None:
|
|
self.particles[x][y] = None
|
|
self.particles[new_x][new_y] = particle
|
|
self.active_particles.add((new_x, new_y))
|
|
self.active_particles.discard((x, y))
|
|
particle.position = (new_x, new_y)
|
|
|
|
|
|
def handle_special_particles(self, particle, x, y):
|
|
"""Handle special particle behaviors"""
|
|
if particle.particle_type in ['fire', 'flame', 'smoke']:
|
|
if random.random() < 0.6: # % chance
|
|
self.particles[x][y] = None
|
|
self.active_particles.discard((x, y))
|
|
|
|
if particle.particle_type in ['fire', 'flame', 'lava']:
|
|
# Create smoke above with proper physics
|
|
if random.random() < 0.65 and y > 0: # % chance for smoke
|
|
properties = self.particle_properties['smoke']
|
|
new_smoke = Particle.from_type((x, y-1), 'smoke', properties)
|
|
if self.particles[x][y-1] is None:
|
|
self.particles[x][y-1] = new_smoke
|
|
self.active_particles.add((x, y-1))
|
|
|
|
else:
|
|
# Handle collision with water
|
|
if particle.particle_type == 'water':
|
|
self.particles[x][y-1] = None
|
|
self.active_particles.discard((x, y-1))
|
|
self.particles[x][y] = None
|
|
self.active_particles.discard((x, y))
|
|
self.particles[x][y] = Particle.from_type((x, y), 'water', self.particle_properties['water'])
|
|
self.active_particles.add((x, y))
|
|
|
|
|
|
def apply_physics(self, dt):
|
|
"""Handle all physics effects"""
|
|
new_active_particles = set()
|
|
|
|
for x, y in list(self.active_particles):
|
|
particle = self.particles[x][y]
|
|
if not particle:
|
|
continue
|
|
|
|
# Skip wall physics - walls are immutable
|
|
if particle.particle_type == 'wall':
|
|
new_active_particles.add((x, y))
|
|
continue
|
|
|
|
# Handle dissipating particles
|
|
if particle.particle_type in ['fire', 'flame']:
|
|
# Clear current position first
|
|
self.particles[x][y] = None
|
|
self.active_particles.discard((x, y))
|
|
|
|
# Handle fire movement
|
|
dx = random.uniform(-0.5, 0.5)
|
|
dy = random.uniform(-1.5, -0.5) # Upward drift
|
|
new_x = int(x + dx)
|
|
new_y = int(y + dy)
|
|
|
|
if 0 <= new_x < self.width and 0 <= new_y < self.height:
|
|
if self.particles[new_x][new_y] is None:
|
|
self.particles[new_x][new_y] = particle
|
|
new_active_particles.add((new_x, new_y))
|
|
|
|
# Generate smoke above the new fire position
|
|
if random.random() < 0.25 and new_y > 0:
|
|
properties = self.particle_properties['smoke']
|
|
new_smoke = Particle(
|
|
position=(new_x, new_y-1),
|
|
velocity=[random.uniform(-0.5, 0.5), -1],
|
|
mass=properties.get('mass', 0.1),
|
|
particle_type='smoke',
|
|
properties=properties
|
|
)
|
|
if self.particles[new_x][new_y-1] is None:
|
|
self.particles[new_x][new_y-1] = new_smoke
|
|
new_active_particles.add((new_x, new_y-1))
|
|
|
|
# Dissipation chance
|
|
if random.random() < 0.02:
|
|
continue
|
|
|
|
continue
|
|
|
|
# Air handling - particles can pass through
|
|
if particle.particle_type == 'air':
|
|
continue
|
|
|
|
# Handle phase transitions
|
|
self.handle_phase_transitions(particle, x, y)
|
|
|
|
# Calculate forces
|
|
fx, fy = self.calculate_forces(particle, x, y)
|
|
|
|
# handle gas particles
|
|
if particle.is_gas:
|
|
# Gas-specific movement
|
|
dx = random.uniform(-1, 1)
|
|
dy = random.uniform(-2, 0) # Bias upward
|
|
new_x = int(x + dx)
|
|
new_y = int(y + dy)
|
|
|
|
self.particles[x][y] = None
|
|
self.active_particles.discard((x, y))
|
|
|
|
if 0 <= new_x < self.width and 0 <= new_y < self.height:
|
|
if self.particles[new_x][new_y] is None:
|
|
self.particles[x][y] = None
|
|
self.particles[new_x][new_y] = particle
|
|
new_active_particles.add((new_x, new_y))
|
|
self.active_particles.discard((x, y))
|
|
continue
|
|
else:
|
|
# Regular particle physics
|
|
particle.velocity[0] += (fx / particle.mass) * dt
|
|
particle.velocity[1] += (fy / particle.mass) * dt
|
|
|
|
if particle.liquid:
|
|
# Enhanced liquid spreading
|
|
spread_chance = 0.8
|
|
if random.random() < spread_chance:
|
|
dx = random.choice([-1, 1])
|
|
if (0 <= x + dx < self.width and
|
|
self.particles[x + dx][y] is None):
|
|
new_x = x + dx
|
|
new_y = y
|
|
self.particles[x][y] = None
|
|
self.particles[new_x][new_y] = particle
|
|
new_active_particles.add((new_x, new_y))
|
|
continue
|
|
|
|
# Update position for non-liquid particles
|
|
new_x = int(x + particle.velocity[0] * dt)
|
|
new_y = int(y + particle.velocity[1] * dt)
|
|
|
|
if 0 <= new_x < self.width and 0 <= new_y < self.height:
|
|
if self.particles[new_x][new_y] is None:
|
|
self.particles[x][y] = None
|
|
self.particles[new_x][new_y] = particle
|
|
|
|
else:
|
|
new_active_particles.add((x, y))
|
|
|
|
self.active_particles = new_active_particles
|
|
|
|
|
|
def clear_particles_circle(self, center_x, center_y):
|
|
"""Clear particles in a circle around the given point based on brush size"""
|
|
brush_size = int(self.brush_size)
|
|
for dx in range(-brush_size, brush_size + 1):
|
|
for dy in range(-brush_size, brush_size + 1):
|
|
if dx*dx + dy*dy <= brush_size*brush_size: # Circle check
|
|
grid_x = (center_x + dx * self.particle_size) // self.particle_size
|
|
grid_y = (center_y + dy * self.particle_size) // self.particle_size
|
|
|
|
if 0 <= grid_x < self.width and 0 <= grid_y < self.height:
|
|
if self.particles[grid_x][grid_y]:
|
|
self.particles[grid_x][grid_y] = None
|
|
self.active_particles.discard((grid_x, grid_y))
|
|
self.remove_from_spatial_grid(grid_x, grid_y)
|
|
|
|
|
|
def mix_liquids(self, liquid1, liquid2):
|
|
"""Handle liquid mixing interactions"""
|
|
if liquid1.temperature != liquid2.temperature:
|
|
avg_temp = (liquid1.temperature + liquid2.temperature) / 2
|
|
liquid1.temperature = avg_temp
|
|
liquid2.temperature = avg_temp
|
|
liquid1.density = self.calculate_density(liquid1.temperature)
|
|
liquid2.density = self.calculate_density(liquid2.temperature)
|
|
liquid1.viscosity = self.calculate_viscosity(liquid1.temperature)
|
|
liquid2.viscosity = self.calculate_viscosity(liquid2.temperature)
|
|
liquid1.color = self.calculate_color(liquid1.temperature)
|
|
liquid2.color = self.calculate_color(liquid2.temperature)
|
|
|
|
|
|
def simulate_step(self, dt):
|
|
"""Run a single step of the simulation"""
|
|
# Update particle positions and physics
|
|
self.apply_gravity(dt)
|
|
self.apply_physics(dt)
|
|
|
|
# Handle state changes and interactions
|
|
self.handle_temperature(dt)
|
|
self.handle_particle_interactions(dt)
|
|
self.burning()
|
|
self.spread_fire()
|
|
|
|
# Update spatial grid
|
|
self.update_spatial_grid() |