laid out and done like most of the ground work
to reorganize the Physics Engine so that it'll be friendly to others to read.
This commit is contained in:
parent
c6c047fdb7
commit
c225632aac
16
README.md
16
README.md
@ -7,25 +7,25 @@ the code is not finished yet, but i will update it as i go.
|
||||
## Main Features
|
||||
|
||||
- Particle Physics
|
||||
- Gravity and wind effects
|
||||
- Gravity and wind effects (maybe on the wind zones)
|
||||
- Temperature dynamics
|
||||
- State transitions (melting, freezing, evaporation)
|
||||
|
||||
- Particle Interactions
|
||||
- Collision detection
|
||||
- Chemical reactions (e.g., water + sand = mud)
|
||||
- Heat transfer between particles
|
||||
- Chemical reactions (e.g., water + sand = wet sand, Lava + lower temperature = molten rock = rock)
|
||||
- Heat transfer between particles (e.g., Things seem to cool off as for heating up that's a different thing)
|
||||
|
||||
- Special Effects
|
||||
- Fire propagation sorta
|
||||
- Smoke generation
|
||||
- Liquid spreading
|
||||
- Liquid spreading (Could be improved )
|
||||
|
||||
- Optimization Features
|
||||
- Spatial partitioning grid
|
||||
- Dormant particle tracking
|
||||
- Batch processing
|
||||
- Static User Interface
|
||||
- Spatial partitioning grid (to reduce calculations)
|
||||
- Dormant particle tracking (to reduce unnecessary calculations)
|
||||
- Batch processing (to reduce unnecessary calculations)
|
||||
- Static User Interface (to reduce unnecessary calculations)
|
||||
|
||||
### **Current Features**
|
||||
|
||||
|
||||
75
physics/REORGANIZATION.md
Normal file
75
physics/REORGANIZATION.md
Normal file
@ -0,0 +1,75 @@
|
||||
# Physics Module Reorganization Plan
|
||||
|
||||
## 1. particle.py
|
||||
|
||||
Contains core particle functionality:
|
||||
|
||||
- Full Particle class definition
|
||||
- particle_properties handling
|
||||
- create_particle()
|
||||
- create_particle_circle()
|
||||
- transform_particle()
|
||||
- get_particle_state()
|
||||
|
||||
## 2. forces.py
|
||||
|
||||
Physics and movement systems:
|
||||
|
||||
- calculate_forces()
|
||||
- _apply_neighbor_forces()
|
||||
- apply_gravity()
|
||||
- apply_physics()
|
||||
- add_wind_zone()
|
||||
- handle_gas_movement()
|
||||
|
||||
## 3. interactions.py
|
||||
|
||||
Particle interaction handling:
|
||||
|
||||
- handle_particle_interactions()
|
||||
- process_interaction()
|
||||
- mix_liquids()
|
||||
- create_mud()
|
||||
- handle_particle_damage()
|
||||
- _get_quick_neighbors()
|
||||
|
||||
## 4. temperature.py
|
||||
|
||||
Temperature and state management:
|
||||
|
||||
- handle_temperature()
|
||||
- handle_phase_transitions()
|
||||
- burning()
|
||||
- spread_fire()
|
||||
- ignite_particle()
|
||||
- handle_special_particles()
|
||||
|
||||
## 5. grid.py
|
||||
|
||||
Spatial management systems:
|
||||
|
||||
- update_spatial_grid()
|
||||
- get_cell_key()
|
||||
- add_to_spatial_grid()
|
||||
- remove_from_spatial_grid()
|
||||
- _get_neighbors_from_grid()
|
||||
- _wake_neighbors()
|
||||
- clear_particles_circle()
|
||||
|
||||
## 6. simulation.py
|
||||
|
||||
Core simulation coordination:
|
||||
|
||||
- Simulation class (main orchestrator)
|
||||
- simulate_step()
|
||||
- track_tps()
|
||||
- reset_particle_count()
|
||||
- get_accurate_particle_count()
|
||||
|
||||
## Migration Steps
|
||||
|
||||
1. Create new files
|
||||
2. Move related code sections
|
||||
3. Update imports
|
||||
4. Test each component
|
||||
5. Update main simulation.py references
|
||||
@ -22,7 +22,7 @@ import time
|
||||
import numpy as np
|
||||
|
||||
engine_settings = {
|
||||
'pause_sim': False,
|
||||
'pause_sim': True,
|
||||
'enable_cursor': True,
|
||||
'enable_glow': False,
|
||||
'enable_gas_effect': True,
|
||||
@ -76,5 +76,5 @@ def load_particle_properties():
|
||||
# Load particle properties once when module is imported
|
||||
particle_properties = load_particle_properties()
|
||||
|
||||
__all__ = ['pygame', 'np', 'random', 'time', 'engine_settings', 'particle_properties', 'cProfile', 'pstats', 'sys', 'os', 'jit']
|
||||
__all__ = ['pygame', 'np', 'random', 'time', 'engine_settings', 'particle_properties', 'cProfile', 'pstats', 'sys', 'os']
|
||||
|
||||
|
||||
0
src/debug/__init__.py
Normal file
0
src/debug/__init__.py
Normal file
@ -60,8 +60,10 @@
|
||||
"velocity": 0.5,
|
||||
"conductivity": 1,
|
||||
"heat_capacity": 1,
|
||||
"color": [139, 69, 19, 255],
|
||||
"color": [125, 45, 55, 255],
|
||||
"mass": 0.5,
|
||||
"melt": "dirt",
|
||||
"melt_temperature": 100,
|
||||
"flamability": 0,
|
||||
"temperature": 20,
|
||||
"explosive": false,
|
||||
|
||||
@ -2,15 +2,13 @@
|
||||
"plasma": {
|
||||
"name": "Plasma",
|
||||
"size": 1,
|
||||
"hardness": 0.001,
|
||||
"velocity": 0.0,
|
||||
"conductivity": 0,
|
||||
"heat_capacity": 10,
|
||||
"hardness": 0.0,
|
||||
"velocity": 0.8,
|
||||
"conductivity": 1,
|
||||
"heat_capacity": 1,
|
||||
"color": [255, 100, 200, 255],
|
||||
"mass": 0.001,
|
||||
"temperature": 3600,
|
||||
"friction": 0.0,
|
||||
"viscosity": 0.0,
|
||||
"mass": 0.01,
|
||||
"temperature": 3400,
|
||||
"liquid": false,
|
||||
"solid": false,
|
||||
"is_gas": true,
|
||||
@ -24,6 +22,7 @@
|
||||
"hardness": 0.1,
|
||||
"velocity": 0.8,
|
||||
"conductivity": 1,
|
||||
"heat_capacity": 1,
|
||||
"color": [255, 255, 0, 255],
|
||||
"mass": 0.01,
|
||||
"temperature": 900,
|
||||
|
||||
69
src/physics/particle.py
Normal file
69
src/physics/particle.py
Normal file
@ -0,0 +1,69 @@
|
||||
|
||||
# Load particle properties from json so we know what particles we got and how they should be simulated.
|
||||
class Particle:
|
||||
def __init__(self, simulation, 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
|
||||
self.sim = simulation
|
||||
|
||||
# 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)
|
||||
self.durability = properties.get("durability", 100.0)
|
||||
|
||||
# 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])
|
||||
self.explosion_force = properties.get("explosion_force", 0)
|
||||
self.explosion_duration = properties.get("explosion_duration", 0)
|
||||
|
||||
# Pressure properties
|
||||
self.pressure_resistance = properties.get("pressure_resistance", 0)
|
||||
self.pressure_tolerance = properties.get("pressure_tolerance", 0)
|
||||
self.pressure_threshold = properties.get("pressure_threshold", 0)
|
||||
self.pressure_threshold_duration = properties.get("pressure_threshold_duration", 0)
|
||||
|
||||
# Burning properties
|
||||
self.burning = properties.get("burning", False)
|
||||
self.burn_temperature = properties.get("burn_temperature", 0)
|
||||
self.burn_duration = properties.get("burn_duration", 0)
|
||||
self.burn_color = properties.get("burn_color", [255, 0, 0])
|
||||
self.burn_rate = properties.get("burn_rate", 0)
|
||||
self.burn_intensity = properties.get("burn_intensity", 0)
|
||||
self.burn_rate_multiplier = properties.get("burn_rate_multiplier", 1.0)
|
||||
|
||||
@classmethod
|
||||
def from_type(cls, simulation, position, particle_type, properties):
|
||||
default_velocity = [0, 0]
|
||||
default_mass = properties.get("mass", 1.0)
|
||||
return cls(simulation, position, default_velocity, default_mass, particle_type, properties)
|
||||
|
||||
|
||||
@ -20,15 +20,17 @@ Key Components:
|
||||
|
||||
#Load the imports.
|
||||
from config.settings import np, time, particle_properties
|
||||
from physics.particle import Particle
|
||||
|
||||
|
||||
# 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):
|
||||
def __init__(self, simulation, 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
|
||||
self.sim = simulation
|
||||
|
||||
# Core properties
|
||||
self.size = properties.get("size", 1)
|
||||
@ -83,12 +85,12 @@ class Particle:
|
||||
self.burn_rate_multiplier = properties.get("burn_rate_multiplier", 1.0)
|
||||
|
||||
@classmethod
|
||||
def from_type(cls, position, particle_type, properties):
|
||||
def from_type(cls, simulation, 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)
|
||||
return cls(simulation, position, default_velocity, default_mass, particle_type, properties)
|
||||
|
||||
|
||||
|
||||
class Simulation:
|
||||
# the main class of the simulation.
|
||||
|
||||
@ -200,8 +202,8 @@ class Simulation:
|
||||
if particle.particle_type == 'lava':
|
||||
if particle.temperature < particle.solidify_temperature:
|
||||
state_changed = self.transform_particle(x, y, particle.solidify)
|
||||
return state_changed
|
||||
|
||||
return state_changed
|
||||
|
||||
# Check evaporation
|
||||
if hasattr(particle, 'evaporate_temperature') and particle.evaporate_temperature is not None:
|
||||
if particle.temperature >= particle.evaporate_temperature and particle.evaporate:
|
||||
@ -294,13 +296,20 @@ class Simulation:
|
||||
|
||||
def process_interaction(self, particle1, particle2, x1, y1, x2, y2): # this function is part 2 of handle_particle_interactions.
|
||||
"""Process specific interactions between two particles"""
|
||||
# Water + Sand = Mud
|
||||
if (particle1.particle_type == 'water' and particle2.particle_type == 'sand' or
|
||||
# Water + Sand = Wet Sand
|
||||
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.create_mud(x1, y1, 'wsand') # Pass wsand type
|
||||
self.particles[x2][y2] = None
|
||||
self.active_particles.discard((x2, y2))
|
||||
|
||||
|
||||
# Water + Dirt = Mud
|
||||
if (particle1.particle_type == 'water' and particle2.particle_type == 'dirt' or
|
||||
particle2.particle_type == 'water' and particle1.particle_type == 'dirt'):
|
||||
self.create_mud(x1, y1, 'mud') # Pass mud type
|
||||
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
|
||||
@ -315,21 +324,30 @@ class Simulation:
|
||||
if np.random.random() < 0.3: # 30% chance to ignite
|
||||
self.transform_particle(target_x, target_y, 'fire')
|
||||
|
||||
# Add plasma effects
|
||||
if particle1.particle_type == 'plasma' or particle2.particle_type == 'plasma':
|
||||
target = particle2 if particle1.particle_type == 'plasma' else particle1
|
||||
target_x, target_y = (x2, y2) if particle1.particle_type == 'plasma' else (x1, y1)
|
||||
|
||||
# Transfer high temperature to target
|
||||
if hasattr(target, 'temperature'):
|
||||
target.temperature += 100 # Rapid temperature increase
|
||||
|
||||
def create_mud(self, x, y): # this is where we create the mud. probably should be moved to handle_particle_interactions or process_interaction.
|
||||
|
||||
if 'mud' in self.particle_properties:
|
||||
properties = self.particle_properties['mud']
|
||||
new_particle = Particle.from_type((x, y), 'mud', properties)
|
||||
|
||||
def create_mud(self, x, y, mud_type):
|
||||
"""Create either wet sand or mud based on the specified type"""
|
||||
if mud_type in self.particle_properties:
|
||||
properties = self.particle_properties[mud_type]
|
||||
new_particle = Particle.from_type(self, (x, y), mud_type, properties)
|
||||
self.particles[x][y] = new_particle
|
||||
self.active_particles.add((x, y))
|
||||
|
||||
|
||||
def transform_particle(self, x, y, new_type): # this is where we transform the particle.
|
||||
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)
|
||||
new_particle = Particle.from_type(self, (x, y), new_type, properties)
|
||||
self.particles[x][y] = new_particle
|
||||
self.active_particles.add((x, y))
|
||||
|
||||
@ -495,12 +513,12 @@ class Simulation:
|
||||
continue
|
||||
|
||||
|
||||
if particle.temperature > 1700:
|
||||
if particle.temperature > 30000:
|
||||
# Transition to gas
|
||||
particle.is_gas = True
|
||||
particle.temperature = 1700
|
||||
particle.temperature = 30000
|
||||
particle.velocity = [np.random.uniform(-1, 1), np.random.uniform(-1, 1)]
|
||||
particle.temperature < 1400
|
||||
particle.temperature < 30000
|
||||
particle.is_gas = False
|
||||
|
||||
# Temperature spread to neighbors
|
||||
@ -511,7 +529,7 @@ class Simulation:
|
||||
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
|
||||
heat_transfer = temp_diff * 0.3 * dt
|
||||
particle.temperature -= heat_transfer
|
||||
neighbor.temperature += heat_transfer
|
||||
|
||||
@ -543,6 +561,7 @@ class Simulation:
|
||||
properties = self.particle_properties[particle_type]
|
||||
position = (grid_x, grid_y)
|
||||
new_particle = Particle(
|
||||
simulation=self,
|
||||
position=position,
|
||||
velocity=[0, 0],
|
||||
mass=properties.get('mass', 1.0),
|
||||
@ -670,6 +689,7 @@ class Simulation:
|
||||
if np.random.random() < 0.25 and new_y > 0:
|
||||
properties = self.particle_properties['smoke']
|
||||
new_smoke = Particle(
|
||||
simulation=self,
|
||||
position=(new_x, new_y-1),
|
||||
velocity=[np.random.uniform(-0.5, 0.5), -1],
|
||||
mass=properties.get('mass', 0.1),
|
||||
|
||||
72
src/physics/tests/base.py
Normal file
72
src/physics/tests/base.py
Normal file
@ -0,0 +1,72 @@
|
||||
|
||||
from config.settings import np, time, engine_settings
|
||||
from src.physics.particle import Particle, particle_properties
|
||||
|
||||
|
||||
|
||||
class SimulationBase:
|
||||
def __init__(self, width: int, height: int, x: int = 0, y: int = 0):
|
||||
self.dormant_particles = set()
|
||||
self.particle_movement_counter = {}
|
||||
self.DORMANT_THRESHOLD = 10
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.new_x = 0
|
||||
self.new_y = 0
|
||||
|
||||
self.particle_size = 3
|
||||
self.particles = [[None for _ in range(height)] for _ in range(width)]
|
||||
print(f"Base init - particles type: {type(self.particles)}")
|
||||
print(f"After initialization - particles type: {type(self.particles)}")
|
||||
self.active_particles = set()
|
||||
self.particle_count = 0
|
||||
self.brush_size = 1
|
||||
self.max_brush_size = 20
|
||||
self.particle_properties = particle_properties
|
||||
self.particle_types = list(self.particle_properties.keys())
|
||||
self.current_particle_type = self.particle_types[0] if self.particle_types else 'sand'
|
||||
self.gravity = 9.8
|
||||
self.wind_zones = []
|
||||
self.wind = [0.0, 0.0]
|
||||
|
||||
|
||||
def create_particle(self, x, y): # this is where we create the particle.
|
||||
if engine_settings ["enable_debug"]:
|
||||
print(f"Before particle creation - particles type: {type(self.particles)}")
|
||||
|
||||
"""Create a new particle with full property support"""
|
||||
particle_type = self.current_particle_type.lower()
|
||||
# Check if the particle is within the grid boundaries
|
||||
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 and
|
||||
self.particles[grid_x][grid_y] is None):
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
self.particles[grid_x][grid_y] = new_particle
|
||||
self.active_particles.add((grid_x, grid_y))
|
||||
self.particle_count += 1
|
||||
|
||||
|
||||
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
|
||||
grid_x = (center_x + dx * self.particle_size) // self.particle_size
|
||||
grid_y = (center_y + dy * self.particle_size) // self.particle_size
|
||||
self.create_particle(grid_x, grid_y)
|
||||
296
src/physics/tests/forces.py
Normal file
296
src/physics/tests/forces.py
Normal file
@ -0,0 +1,296 @@
|
||||
"""
|
||||
Physics force calculations and movement systems for Sandpypi
|
||||
Handles particle movement, forces, and physics calculations
|
||||
"""
|
||||
from config.settings import np
|
||||
from src.physics.particle import Particle
|
||||
|
||||
class ForceSystem:
|
||||
def __init__(self, simulation):
|
||||
self.sim = simulation
|
||||
self.wind_zones = []
|
||||
|
||||
|
||||
def calculate_forces(self, particle, x, y):
|
||||
"""Calculate net forces acting on a particle."""
|
||||
# Initialize forces as numpy array for vectorized operations
|
||||
forces = np.zeros(2, dtype=np.float32) # [fx, fy]
|
||||
|
||||
# Vectorized wind zone calculations
|
||||
if self.wind_zones:
|
||||
positions = np.array([[zone['x'], zone['y']] for zone in self.wind_zones])
|
||||
directions = np.array([zone['direction'] for zone in self.wind_zones])
|
||||
strengths = np.array([zone['strength'] for zone in self.wind_zones])
|
||||
radii = np.array([zone['radius'] for zone in self.wind_zones])
|
||||
|
||||
# Calculate distances vectorized
|
||||
dx = x - positions[:, 0]
|
||||
dy = y - positions[:, 1]
|
||||
distances = np.sqrt(dx**2 + dy**2)
|
||||
|
||||
# Apply wind zone forces where distance <= radius
|
||||
mask = distances <= radii
|
||||
scale_factors = np.where(mask, (1 - distances/radii) * strengths[:, np.newaxis], 0)
|
||||
forces += np.sum(directions * scale_factors[:, np.newaxis], axis=0)
|
||||
|
||||
# Apply global wind with vectorized operation
|
||||
wind_factor = 0.5 if particle.is_gas else 1.0
|
||||
forces += np.array(self.wind) * wind_factor
|
||||
|
||||
# Apply drag force vectorized
|
||||
drag = particle.viscosity * -1
|
||||
forces += drag * np.array(particle.velocity)
|
||||
|
||||
# Neighbor forces using numpy arrays
|
||||
neighbors = np.array(self._get_neighbors_from_grid(x, y))
|
||||
if len(neighbors):
|
||||
valid_mask = (
|
||||
(neighbors[:, 0] >= 0) &
|
||||
(neighbors[:, 0] < self.width) &
|
||||
(neighbors[:, 1] >= 0) &
|
||||
(neighbors[:, 1] < self.height)
|
||||
)
|
||||
neighbors = neighbors[valid_mask]
|
||||
|
||||
for nx, ny in neighbors:
|
||||
if (nx, ny) != (x, y):
|
||||
neighbor = self.sim.particles[nx][ny]
|
||||
if neighbor:
|
||||
self._apply_neighbor_forces(particle, neighbor, forces[0], forces[1])
|
||||
|
||||
return forces[0], forces[1]
|
||||
|
||||
|
||||
def _apply_neighbor_forces(self, particle, neighbor, fx, fy):
|
||||
"""Optimized neighbor force calculation"""
|
||||
if hasattr(neighbor, 'temperature') and hasattr(particle, 'temperature'):
|
||||
temp_diff = neighbor.temperature - particle.temperature
|
||||
fy += temp_diff * 0.05
|
||||
|
||||
|
||||
def apply_gravity(self, dt):
|
||||
"""Handle only gravity and basic particle movement"""
|
||||
self.sim.spatial_grid.spatial_grid.clear()
|
||||
|
||||
for x, y in list(self.sim.active_particles):
|
||||
particle = self.sim.particles[x][y]
|
||||
if not particle or particle.particle_type in ['wall', 'stone', 'wood']:
|
||||
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', 'snow', 'ice']:
|
||||
if self.sim.particles[x][new_y] is None:
|
||||
new_x, new_y = x, y + 1
|
||||
else:
|
||||
# Define diagonal directions first
|
||||
diagonal_dirs = [(-1, 1), (1, 1)]
|
||||
diagonal_dirs = np.array(diagonal_dirs)
|
||||
# Randomize movement directions
|
||||
diagonal_dirs = np.random.permutation(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.sim.particles[test_x][test_y] is None):
|
||||
if np.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.sim.particles[x][new_y] is None:
|
||||
new_x = x
|
||||
new_y = y + 1
|
||||
else:
|
||||
spread_directions = np.array([(-1, 0), (1, 0)])
|
||||
np.random.shuffle(spread_directions)
|
||||
for dx, _ in spread_directions:
|
||||
test_x = x + dx
|
||||
if (0 <= test_x < self.width and
|
||||
self.sim.particles[test_x][y] is None):
|
||||
new_x = test_x
|
||||
new_y = y
|
||||
break
|
||||
|
||||
# Move particle if destination is empty
|
||||
if self.sim.particles[new_x][new_y] is None:
|
||||
self.sim.particles[x][y] = None
|
||||
self.sim.particles[new_x][new_y] = particle
|
||||
self.sim.particles.add((new_x, new_y))
|
||||
self.sim.particles.discard((x, y))
|
||||
particle.position = (new_x, new_y)
|
||||
|
||||
|
||||
def apply_physics(self, dt, engine_settings):
|
||||
"""Handle all physics effects"""
|
||||
new_active_particles = set()
|
||||
updates = []
|
||||
|
||||
for x, y in list(self.sim.active_particles):
|
||||
particle = self.sim.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.sim.particles[x][y] = None
|
||||
self.sim.particles.discard((x, y))
|
||||
|
||||
# Handle fire movement using numpy random
|
||||
dx = np.random.uniform(-0.5, 0.5)
|
||||
dy = np.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.sim.particles[new_x][new_y] is None:
|
||||
self.sim.particles[new_x][new_y] = particle
|
||||
new_active_particles.add((new_x, new_y))
|
||||
|
||||
# Generate smoke above with numpy random
|
||||
if np.random.random() < 0.25 and new_y > 0:
|
||||
properties = self.sim.particle_properties['smoke']
|
||||
new_smoke = Particle(
|
||||
position=(new_x, new_y-1),
|
||||
velocity=[np.random.uniform(-0.5, 0.5), -1],
|
||||
mass=properties.get('mass', 0.1),
|
||||
particle_type='smoke',
|
||||
properties=properties
|
||||
)
|
||||
if self.sim.particles[new_x][new_y-1] is None:
|
||||
self.sim.particles[new_x][new_y-1] = new_smoke
|
||||
new_active_particles.add((new_x, new_y-1))
|
||||
|
||||
# Dissipation chance using numpy random
|
||||
if np.random.random() < 0.02:
|
||||
continue
|
||||
continue
|
||||
|
||||
# Air handling
|
||||
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 movement with numpy random
|
||||
dx = np.random.uniform(2, -1)
|
||||
dy = np.random.uniform(-2, 0) # Bias upward
|
||||
new_x = int(x + dx)
|
||||
new_y = int(y + dy)
|
||||
|
||||
self.sim.particles[x][y] = None
|
||||
self.sim.particles.discard((x, y))
|
||||
|
||||
if 0 <= new_x < self.width and 0 <= new_y < self.height:
|
||||
if self.sim.particles[new_x][new_y] is None:
|
||||
self.sim.particles[x][y] = None
|
||||
self.sim.particles[new_x][new_y] = particle
|
||||
new_active_particles.add((new_x, new_y))
|
||||
self.sim.particles.discard((x, y))
|
||||
continue
|
||||
else:
|
||||
# Regular particle physics
|
||||
mass = max(particle.mass, 0.001)
|
||||
particle.velocity[0] += (fx / mass) * dt
|
||||
particle.velocity[1] += (fy / mass) * dt
|
||||
|
||||
if particle.liquid:
|
||||
# Enhanced liquid spreading with numpy random
|
||||
if np.random.random() < 0.5:
|
||||
dx = np.random.choice([-1, 1])
|
||||
if (0 <= x + dx < self.width and
|
||||
self.sim.particles[x + dx][y] is None):
|
||||
new_x = x + dx
|
||||
new_y = y
|
||||
self.sim.particles[x][y] = None
|
||||
self.sim.particles[new_x][new_y] = particle
|
||||
new_active_particles.add((new_x, new_y))
|
||||
continue
|
||||
|
||||
new_x = int(x + particle.velocity[0] * dt)
|
||||
new_y = int(y + particle.velocity[1] * dt)
|
||||
|
||||
# Update position
|
||||
if 0 <= new_x < self.width and 0 <= new_y < self.height:
|
||||
if self.sim.particles[new_x][new_y] is None:
|
||||
updates.append((x, y, new_x, new_y, particle))
|
||||
new_active_particles.add((new_x, new_y))
|
||||
self._wake_neighbors(new_x, new_y)
|
||||
else:
|
||||
new_active_particles.add((x, y))
|
||||
|
||||
# Apply updates
|
||||
for old_x, old_y, new_x, new_y, particle in updates:
|
||||
self.sim.particles[old_x][old_y] = None
|
||||
self.sim.particles[new_x][new_y] = particle
|
||||
particle.position = (new_x, new_y)
|
||||
|
||||
# Handle boundaries
|
||||
if engine_settings['outerwall']:
|
||||
if x <= 0 or x >= self.width-1 or y <= 0 or y >= self.height-1:
|
||||
if self.sim.particles[x][y] is None:
|
||||
properties = self.sim.particle_properties['wall']
|
||||
wall = Particle.from_type((x, y), 'wall', properties)
|
||||
self.sim.particles[x][y] = wall
|
||||
new_active_particles.add((x, y))
|
||||
self.particle_count += 1
|
||||
continue
|
||||
else:
|
||||
if x <= 0 or x >= self.width-1 or y <= 0 or y >= self.height-1:
|
||||
if self.sim.particles[x][y] is not None:
|
||||
self.sim.particles[x][y] = None
|
||||
self.sim.particles.discard((x, y))
|
||||
self.sim.particle_count -= 1
|
||||
continue
|
||||
|
||||
self.sim.particles = new_active_particles
|
||||
|
||||
|
||||
def add_wind_zone(self, x, y):
|
||||
# Instead of creating particles, store wind zone data
|
||||
wind_zone = {
|
||||
'x': x,
|
||||
'y': y,
|
||||
'radius': 50,
|
||||
'strength': 2.0,
|
||||
'direction': [1, 0]
|
||||
}
|
||||
self.wind_zones.append(wind_zone)
|
||||
|
||||
|
||||
def handle_gas_movement(self, particle, x, y): # this is where we handle the gas movement. this function sucks. wip
|
||||
"""Handle gas particle movement"""
|
||||
if particle.is_gas:
|
||||
dx = np.random.uniform(-1, 1)
|
||||
dy = np.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.sim.particles[new_x][new_y] is None:
|
||||
self.sim.particles[x][y] = None
|
||||
self.sim.particles[new_x][new_y] = particle
|
||||
self.sim.particles.add((new_x, new_y))
|
||||
self.sim.particles.discard((x, y))
|
||||
87
src/physics/tests/grid.py
Normal file
87
src/physics/tests/grid.py
Normal file
@ -0,0 +1,87 @@
|
||||
|
||||
"""
|
||||
Spatial grid management systems for Sandpypi
|
||||
Handles particle positioning and neighbor calculations
|
||||
"""
|
||||
from physics.base import SimulationBase
|
||||
|
||||
|
||||
class SpatialGrid:
|
||||
def __init__(self, simulation):
|
||||
self.sim = simulation
|
||||
self.width = simulation.width
|
||||
self.height = simulation.height
|
||||
self.cell_size = 32
|
||||
self.spatial_grid = {}
|
||||
|
||||
|
||||
def update_spatial_grid(self):
|
||||
"""Enhanced spatial grid update"""
|
||||
if len(self.sim.active_particles) > 100:
|
||||
self.spatial_grid = {}
|
||||
cell_lists = {}
|
||||
|
||||
# Track temperature-sensitive particles
|
||||
temp_sensitive_cells = set()
|
||||
|
||||
for x, y in self.sim.active_particles:
|
||||
cell_key = (x // self.cell_size, y // self.cell_size)
|
||||
if cell_key not in cell_lists:
|
||||
cell_lists[cell_key] = []
|
||||
cell_lists[cell_key].append((x, y))
|
||||
|
||||
# Mark cells with temperature-sensitive particles
|
||||
particle = self.sim.particles[x][y]
|
||||
if hasattr(particle, 'solidify_temperature') or hasattr(particle, 'melt_temperature'):
|
||||
temp_sensitive_cells.add(cell_key)
|
||||
|
||||
self.spatial_grid = {k: set(v) for k, v in cell_lists.items()}
|
||||
return temp_sensitive_cells
|
||||
|
||||
|
||||
def get_cell_key(self, x, y): # this is where we get the cell key.
|
||||
# Convert coordinates to grid cell
|
||||
cell_x = x // self.cell_size
|
||||
cell_y = y // self.cell_size
|
||||
return (cell_x, cell_y)
|
||||
|
||||
|
||||
def _get_neighbors_from_grid(self, x, y):
|
||||
"""Get neighbors using spatial grid"""
|
||||
cell_key = self.get_cell_key(x, y)
|
||||
neighbors = []
|
||||
|
||||
# Check current and adjacent cells
|
||||
for dx in [-1, 0, 1]:
|
||||
for dy in [-1, 0, 1]:
|
||||
check_key = (cell_key[0] + dx, cell_key[1] + dy)
|
||||
if check_key in self.spatial_grid:
|
||||
neighbors.extend(self.spatial_grid[check_key])
|
||||
|
||||
return neighbors
|
||||
|
||||
|
||||
def add_to_spatial_grid(self, particle, x, y): # this is where we add to the spatial grid.
|
||||
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))
|
||||
return cell_key
|
||||
|
||||
|
||||
def remove_from_spatial_grid(self, x, y): # this is where we remove from the spatial grid.
|
||||
cell_key = self.get_cell_key(x, y)
|
||||
if cell_key in self.spatial_grid:
|
||||
self.spatial_grid[cell_key].discard((x, y))
|
||||
return cell_key
|
||||
|
||||
|
||||
def _wake_neighbors(self, x, y):
|
||||
for dx in [-1, 0, 1]:
|
||||
for dy in [-1, 0, 1]:
|
||||
nx, ny = x + dx, y + dy
|
||||
key = (nx, ny)
|
||||
if key in self.dormant_particles:
|
||||
self.dormant_particles.discard(key)
|
||||
self.particle_movement_counter[key] = 0
|
||||
|
||||
118
src/physics/tests/interactions.py
Normal file
118
src/physics/tests/interactions.py
Normal file
@ -0,0 +1,118 @@
|
||||
|
||||
"""
|
||||
Particle interaction handling systems for Sandpypi
|
||||
Manages how different particle types interact with each other
|
||||
"""
|
||||
from config.settings import np
|
||||
from src.physics.particle import Particle
|
||||
from physics.base import SimulationBase
|
||||
|
||||
|
||||
class ParticleInteractions:
|
||||
def __init__(self, simulation):
|
||||
self.sim = simulation
|
||||
|
||||
def handle_particle_interactions(self, dt): # this is where we handle all the particle interactions.
|
||||
"""Handle interactions between different particle types"""
|
||||
for x, y in list(self.sim.active_particles):
|
||||
particle = self.sim.particles[x][y]
|
||||
|
||||
if not particle:
|
||||
continue
|
||||
|
||||
# Handle damage for any particle with durability
|
||||
self.handle_particle_damage(particle, x, y)
|
||||
|
||||
# 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): # this function is part 2 of handle_particle_interactions.
|
||||
"""Process specific interactions between two particles"""
|
||||
# Water + Sand = Wet Sand
|
||||
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, 'wsand') # Pass wsand type
|
||||
self.particles[x2][y2] = None
|
||||
self.active_particles.discard((x2, y2))
|
||||
|
||||
# Water + Dirt = Mud
|
||||
if (particle1.particle_type == 'water' and particle2.particle_type == 'dirt' or
|
||||
particle2.particle_type == 'water' and particle1.particle_type == 'dirt'):
|
||||
self.create_mud(x1, y1, 'mud') # Pass mud type
|
||||
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 np.random.random() < 0.3: # 30% chance to ignite
|
||||
self.transform_particle(target_x, target_y, 'fire')
|
||||
|
||||
|
||||
def mix_liquids(self, liquid1, liquid2): # not implemented
|
||||
"""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 create_mud(self, x, y, mud_type):
|
||||
"""Create either wet sand or mud based on the specified type"""
|
||||
if mud_type in self.particle_properties:
|
||||
properties = self.particle_properties[mud_type]
|
||||
new_particle = Particle.from_type((x, y), mud_type, properties)
|
||||
self.particles[x][y] = new_particle
|
||||
self.active_particles.add((x, y))
|
||||
|
||||
def handle_particle_damage(self, particle, x, y):
|
||||
"""Handle damage calculations for particles with durability"""
|
||||
if not hasattr(particle, 'durability'):
|
||||
return
|
||||
|
||||
# Pressure damage
|
||||
fx, fy = self.calculate_forces(particle, x, y)
|
||||
pressure = (fx*fx + fy*fy)**0.5 # Calculate magnitude of force vector
|
||||
|
||||
if hasattr(particle, 'pressure_threshold') and pressure > particle.pressure_threshold:
|
||||
particle.durability -= 0.1
|
||||
|
||||
# Impact damage
|
||||
neighbors = self._get_neighbors_from_grid(x, y)
|
||||
for nx, ny in neighbors:
|
||||
neighbor = self.particles[nx][ny]
|
||||
if neighbor and neighbor.velocity[1] > 5.0:
|
||||
particle.durability -= 0.2
|
||||
|
||||
# Heat damage
|
||||
if particle.temperature > 900:
|
||||
particle.durability -= 1
|
||||
|
||||
# Check if particle should break
|
||||
if particle.durability <= 0 and hasattr(particle, 'broken'):
|
||||
self.transform_particle(x, y, particle.broken)
|
||||
return
|
||||
|
||||
|
||||
def _get_quick_neighbors(self, x, y):
|
||||
"""Quick neighbor lookup without full spatial grid"""
|
||||
return [(x+dx, y+dy) for dx, dy in [(-1,0), (1,0), (0,-1), (0,1)]]
|
||||
73
src/physics/tests/simulation.py
Normal file
73
src/physics/tests/simulation.py
Normal file
@ -0,0 +1,73 @@
|
||||
|
||||
from physics.base import SimulationBase, time, np
|
||||
from physics.grid import SpatialGrid as sg
|
||||
from physics.forces import ForceSystem as fs
|
||||
from physics.temperature import TemperatureSystem as ts
|
||||
from physics.interactions import ParticleInteractions as pi
|
||||
from src.physics.particle import Particle, particle_properties
|
||||
from physics.sim import Simulation
|
||||
|
||||
class Simulation(SimulationBase):
|
||||
def __init__(self, width, height, x=0, y=0):
|
||||
print(f"Simulation init - before base init - particles type: {type(getattr(self, 'particles', None))}")
|
||||
|
||||
SimulationBase.__init__(self, width, height, x, y)
|
||||
|
||||
print(f"After base init - particles type: {type(self.particles)}")
|
||||
self.dormant_particles = set()
|
||||
self.particle_movement_counter = {}
|
||||
self.DORMANT_THRESHOLD = 10
|
||||
self.sim = Simulation(width, height)
|
||||
self.spatial_grid = sg(self)
|
||||
self.force_system = fs(self)
|
||||
self.temperature_system = ts(self)
|
||||
self.particle_interactions = pi(self)
|
||||
|
||||
def simulate_step(self, dt, engine_settings):
|
||||
"""Run simulation step with spatial grid updates"""
|
||||
self.spatial_grid.update_spatial_grid()
|
||||
|
||||
# Update particle positions and physics
|
||||
self.force_system.apply_gravity(dt)
|
||||
self.sim.Simulation.apply_physics(dt, engine_settings)
|
||||
|
||||
# Handle state changes and interactions
|
||||
self.temperature_system.handle_temperature(dt)
|
||||
self.particle_interactions.handle_particle_interactions(dt)
|
||||
self.temperature_system.burning()
|
||||
self.temperature_system.spread_fire()
|
||||
|
||||
|
||||
def track_tps(self):
|
||||
"""Track Ticks Per Second for simulation performance monitoring"""
|
||||
if not hasattr(self, '_tps_counter'):
|
||||
self._tps_counter = 0
|
||||
self._tps_timer = time.time()
|
||||
self._current_tps = 0
|
||||
|
||||
self._tps_counter += 1
|
||||
current_time = time.time()
|
||||
elapsed = current_time - self._tps_timer
|
||||
|
||||
# Update TPS count every second
|
||||
if elapsed >= 1.0:
|
||||
self._current_tps = self._tps_counter / elapsed
|
||||
self._tps_counter = 0
|
||||
self._tps_timer = current_time
|
||||
|
||||
return self._current_tps
|
||||
|
||||
|
||||
def reset_particle_count(self):
|
||||
"""Reset and recalculate accurate particle count"""
|
||||
active_count = np.sum([1 for x, y in self.active_particles if self.particles[x][y] is not None])
|
||||
self.particle_count = int(active_count)
|
||||
|
||||
|
||||
def get_accurate_particle_count(self):
|
||||
"""Get current accurate particle count using numpy"""
|
||||
particle_mask = np.array([[self.particles[x][y] is not None
|
||||
for y in range(self.height)]
|
||||
for x in range(self.width)])
|
||||
return np.sum(particle_mask)
|
||||
|
||||
162
src/physics/tests/temperature.py
Normal file
162
src/physics/tests/temperature.py
Normal file
@ -0,0 +1,162 @@
|
||||
|
||||
"""
|
||||
Temperature and state management systems for Sandpypi
|
||||
Handles temperature changes, phase transitions, and burning mechanics
|
||||
"""
|
||||
from config.settings import np
|
||||
|
||||
|
||||
|
||||
class TemperatureSystem:
|
||||
def __init__(self, simulation):
|
||||
self.sim = simulation
|
||||
|
||||
def handle_temperature(self, dt): # this is where we handle the temperature.
|
||||
"""Handle temperature changes and state transitions"""
|
||||
for x, y in list(self.sim.active_particles):
|
||||
particle = self.sim.particles[x][y]
|
||||
if particle and particle.is_gas:
|
||||
particle.temperature -= 0.5 * dt
|
||||
if not particle:
|
||||
continue
|
||||
|
||||
|
||||
if particle.temperature > 1700:
|
||||
# Transition to gas
|
||||
particle.is_gas = True
|
||||
particle.temperature = 1700
|
||||
particle.velocity = [np.random.uniform(-1, 1), np.random.uniform(-1, 1)]
|
||||
particle.temperature < 1400
|
||||
particle.is_gas = False
|
||||
|
||||
# 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.sim.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 handle_phase_transitions(self, particle, x, y): # this is where we handle all the phase transitions.
|
||||
"""Handle all phase transitions for a particle"""
|
||||
state_changed = False
|
||||
|
||||
if particle.particle_type == 'lava':
|
||||
if particle.temperature < particle.solidify_temperature:
|
||||
state_changed = self.transform_particle(x, y, particle.solidify)
|
||||
return state_changed
|
||||
|
||||
# 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 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)
|
||||
|
||||
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.sim.particle:
|
||||
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.sim.particle_properties:
|
||||
self.transform_particle(x, y, new_type)
|
||||
return new_type
|
||||
|
||||
if particle.particle_type == 'steam':
|
||||
# Steam should condense when it cools
|
||||
if particle.temperature <= particle.solidify_temperature:
|
||||
self.transform_particle(x, y, new_type)
|
||||
return new_type
|
||||
|
||||
# Check durability property from JSON
|
||||
if (hasattr(particle, 'durability') and hasattr(particle, 'brk')
|
||||
and particle.brk is not None):
|
||||
if particle.durability <= 0:
|
||||
self.transform_particle(x, y, particle.broken)
|
||||
return new_type
|
||||
|
||||
|
||||
def burning(self): # this is where we handle the burning.
|
||||
"""Handle burning of particles."""
|
||||
for x, y in list(self.sim.active_particles):
|
||||
particle = self.sim.particles[x][y]
|
||||
if particle and hasattr(particle, 'burning') and particle.burning:
|
||||
particle.temperature += 10
|
||||
if particle.temperature > 1000:
|
||||
self.sim.particles[x][y] = None
|
||||
self.sim.active_particles.remove((x, y))
|
||||
self.spatial_grid.pop((x, y), None)
|
||||
|
||||
|
||||
def spread_fire(self): # this is where we spread the fire.
|
||||
"""Spread fire to neighboring particles."""
|
||||
for x, y in list(self.sim.active_particles):
|
||||
particle = self.sim.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.sim.particles[nx][ny]
|
||||
if neighbor and hasattr(neighbor, 'flamability'):
|
||||
if neighbor.particle_type == 'wood':
|
||||
# Higher chance to ignite wood
|
||||
if np.random.random() < 0.3: # 30% chance to spread
|
||||
self.ignite_particle(neighbor)
|
||||
elif neighbor.flamability > 0:
|
||||
if np.random.random() < 0.1: # 10% chance for other materials
|
||||
self.ignite_particle(neighbor)
|
||||
|
||||
|
||||
def ignite_particle(self, particle): # this is where we ignite the 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 # burn time
|
||||
|
||||
def handle_special_particles(self, particle, x, y): # this is where we handle special particles.
|
||||
"""Handle special particle behaviors"""
|
||||
if particle.particle_type in ['fire', 'flame', 'smoke']:
|
||||
if np.random.random() < 0.6: # % chance
|
||||
self.sim.particles[x][y] = None
|
||||
self.sim.active_particles.discard((x, y))
|
||||
|
||||
if particle.particle_type in ['fire', 'flame', 'lava']:
|
||||
# Create smoke above with proper physics
|
||||
if np.random.random() < 0.65 and y > 0: # % chance for smoke
|
||||
properties = self.sim.particle_properties['smoke']
|
||||
new_smoke = self.sim.Particle.from_type((x, y-1), 'smoke', properties)
|
||||
if self.sim.particles[x][y-1] is None:
|
||||
self.sim.particles[x][y-1] = new_smoke
|
||||
self.sim.active_particles.add((x, y-1))
|
||||
|
||||
else:
|
||||
# Handle collision with water
|
||||
if particle.particle_type == 'water':
|
||||
self.sim.particles[x][y-1] = None
|
||||
self.sim.active_particles.discard((x, y-1))
|
||||
self.sim.particles[x][y] = None
|
||||
self.sim.active_particles.discard((x, y))
|
||||
self.sim.particles[x][y] = self.sim.Particle.from_type((x, y), 'water', self.sim.particle_properties['water'])
|
||||
self.sim.active_particles.add((x, y))
|
||||
@ -23,7 +23,6 @@ The `clear_screen` function is used to reset the simulation grid and clear the d
|
||||
|
||||
|
||||
from config.settings import pygame, random, particle_properties, engine_settings
|
||||
from debug.debugger_system import DebuggerSystem
|
||||
|
||||
|
||||
class Rendering:
|
||||
@ -49,7 +48,6 @@ class Rendering:
|
||||
'zoom_window': pygame.font.SysFont(None, 20),
|
||||
'zoom_window_text': pygame.font.SysFont(None, 20)
|
||||
}
|
||||
self.debug = DebuggerSystem()
|
||||
|
||||
# Pre-render static UI elements
|
||||
self.button_surfaces = {}
|
||||
@ -286,13 +284,52 @@ class Rendering:
|
||||
|
||||
|
||||
def draw_debug_overlay(self, fps, sim):
|
||||
"""Draws debug overlay on the screen."""
|
||||
if engine_settings['enable_fps'] or engine_settings['enable_debug']:
|
||||
self.debug.screen = self.screen # Pass screen reference
|
||||
self.debug.cached_fonts = self.cached_fonts # Pass font references
|
||||
self.debug.track_fps()
|
||||
self.debug.update_performance_metrics(sim)
|
||||
self.debug.draw_debug_overlay(self.screen, sim)
|
||||
if not engine_settings['enable_fps'] and not engine_settings['enable_debug']:
|
||||
return
|
||||
|
||||
# Create static debug surface if not exists
|
||||
if not hasattr(self, 'debug_surface'):
|
||||
self.debug_surface = pygame.Surface((300, 150), pygame.SRCALPHA)
|
||||
|
||||
# Only update when values change significantly
|
||||
mouse_x, mouse_y = pygame.mouse.get_pos()
|
||||
grid_x, grid_y = mouse_x // 3, mouse_y // 3
|
||||
|
||||
current_info = (
|
||||
int(fps),
|
||||
int(sim.track_tps()),
|
||||
mouse_x // 10, # Reduced update frequency
|
||||
mouse_y // 10,
|
||||
sim.particle_count
|
||||
)
|
||||
|
||||
if not hasattr(self, '_last_debug_info') or current_info != self._last_debug_info:
|
||||
self._last_debug_info = current_info
|
||||
self.debug_surface.fill((0, 0, 0, 0))
|
||||
|
||||
font = self.cached_fonts['debug']
|
||||
y_offset = 10
|
||||
|
||||
if engine_settings['enable_fps']:
|
||||
fps_surf = font.render(f"FPS: {fps:.1f} | TPS: {sim.track_tps():.1f}", True, (255, 255, 255))
|
||||
self.debug_surface.blit(fps_surf, (10, y_offset))
|
||||
y_offset += 25
|
||||
|
||||
if engine_settings['enable_debug']:
|
||||
debug_lines = [
|
||||
f"Mouse: ({mouse_x}, {mouse_y})",
|
||||
f"Grid: ({grid_x}, {grid_y})",
|
||||
f"Particles: {sim.particle_count}"
|
||||
]
|
||||
|
||||
for line in debug_lines:
|
||||
text_surf = font.render(line, True, (255, 255, 255))
|
||||
self.debug_surface.blit(text_surf, (10, y_offset))
|
||||
y_offset += 25
|
||||
|
||||
# Single blit of cached surface
|
||||
self.screen.blit(self.debug_surface, (0, 0))
|
||||
|
||||
|
||||
def draw_buttons(self): # this is the function that draws the buttons
|
||||
self.buttons = {}
|
||||
|
||||
@ -20,12 +20,6 @@ from config.settings import pygame, engine_settings, cProfile, pstats, time
|
||||
from rendering.rendering import Rendering
|
||||
from physics.sim import Simulation
|
||||
|
||||
"""
|
||||
This is for the future physics engine until i figure out a better method used for testing right now.
|
||||
#import os
|
||||
#import sys
|
||||
#sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
"""
|
||||
|
||||
|
||||
def handle_input(event, sim, rendering, settings_visible, zoom_active, zoom_locked, zoom_pos):
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user