From 986750c7220a887140a7f9b47f3f8abf335dc829 Mon Sep 17 00:00:00 2001 From: Stan44 Date: Fri, 27 Dec 2024 07:39:31 -0600 Subject: [PATCH] Massive changes to the repository have been made. feat: Performance optimizations and UI improvements - Added dormant state tracking for static particles - Fixed settings menu particle spawn overlap - Optimized particle batch processing - Added brush cursor visualization - Improved UI interaction zones - Enhanced gas particle effects - Added glow toggle functionality disabled by default due to performance impact - Fixed particle rendering issues - Debug Overlay Disabled by default - FPS counter added semi seperate from Debug Overlay, Turn this one off when using Debug Overlay - Improved particle rendering performance Performance improvements focus on reducing unnecessary calculations for static particles while maintaining core simulation mechanics. UI changes prevent unwanted particle spawning during menu interactions. --- .gitignore | 11 +- README.md | 23 + __init__.py | 1 + __pycache__/imports.cpython-312.pyc | Bin 220 -> 0 bytes __pycache__/rendering.cpython-312.pyc | Bin 15970 -> 0 bytes __pycache__/sim.cpython-312.pyc | Bin 31611 -> 0 bytes particles.json | 820 +++++++++++++------------- rendering.py | 279 ++++++--- sandpypi.py | 96 +-- settings.py | 23 +- setup.py | 8 - sim.py | 175 ++++-- simulation_core.pyx | 36 -- template_particles.json | 5 +- 14 files changed, 865 insertions(+), 612 deletions(-) create mode 100644 __init__.py delete mode 100644 __pycache__/imports.cpython-312.pyc delete mode 100644 __pycache__/rendering.cpython-312.pyc delete mode 100644 __pycache__/sim.cpython-312.pyc delete mode 100644 setup.py delete mode 100644 simulation_core.pyx diff --git a/.gitignore b/.gitignore index d6bab80..fb7e93c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,8 @@ # ---> Python # Byte-compiled / optimized / DLL files -__pycache__/ *.py[cod] *$py.class - +__pycache__/ # C extensions *.so @@ -160,9 +159,17 @@ cython_debug/ # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ +# Custom Ignores current_stations.html forecast_data.json openapi.json __pycache__/sim.cpython-312.pyc __pycache__/sim.cpython-312.pyc __pycache__/rendering.cpython-312.pyc +sandpypi.dist/ +sandpypi.build/ +sandpypi.onefile-build/ +sandpypi.exe +sandpypi.7z +unittest/ +.7z \ No newline at end of file diff --git a/README.md b/README.md index 97206ac..9e62a2b 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,29 @@ mostly a concept in python for falling sand simulation i guess the goal is to make a python version of the powder toy which is a falling sand sim the code is not finished yet, but i will update it as i go. +Main Features: +------------- +- Particle Physics + - Gravity and wind effects + - Temperature dynamics + - State transitions (melting, freezing, evaporation) + +- Particle Interactions + - Collision detection + - Chemical reactions (e.g., water + sand = mud) + - Heat transfer between particles + +- Special Effects + - Fire propagation + - Smoke generation + - Liquid spreading + +- Optimization Features + - Spatial partitioning grid + - Dormant particle tracking + - Batch processing + """ + ## **Current Features** | **Working** | **Partial** | **Not Working/Implemented** | diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/__init__.py @@ -0,0 +1 @@ + diff --git a/__pycache__/imports.cpython-312.pyc b/__pycache__/imports.cpython-312.pyc deleted file mode 100644 index d09fc42e1b5da35deac521a68a5161f9843826fa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 220 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 F{{dSQSo8n@ diff --git a/particles.json b/particles.json index 1d4bccb..ae2a842 100644 --- a/particles.json +++ b/particles.json @@ -1,331 +1,333 @@ { - "sand": { - "name": "Sand", - "size": 1, - "hardness": 0.5, - "color": [255, 255, 0, 255], - "velocity": 0.5, - "mass": 0.5, - "conductivity": 0, - "heat_capacity": 1, - "flamability": 0.8, - "temperature": 0, - "explosive": false, - "explosion_radius": 0, - "explosion_color": [0, 0, 0], - "friction": 0.5, - "viscosity": 0, - "pressure": 0, - "melt": "molten-Glass", - "melt_temperature": 1000, - "conductive": false, - "liquid": false, - "solid": true, - "is_gas": false - }, - "water": { - "name": "Water", - "size": 1, - "hardness": 0.2, - "velocity": 0.3, - "conductivity": 1, - "heat_capacity": 1, - "color": [0, 0, 255, 255], - "mass": 1, - "flamability": 0, - "temperature": 22, - "explosive": false, - "explosion_radius": 0, - "explosion_color": [0, 0, 0], - "friction": 1, - "viscosity": 1, - "pressure": 0.5, - "evaporate": "steam", - "evaporate_temperature": 145, - "freeze": "ice", - "freeze_temperature": 0, - "melt": "water", - "melt_temperature": 20, - "liquid": true, - "solid": false, - "is_gas": false - }, - "steam": { - "name": "Steam", - "size": 1, - "hardness": 0.0, - "velocity": 0.2, - "conductivity": 1, - "heat_capacity": 1, - "color": [255, 255, 255, 255], - "mass": 0.01, - "flamability": 0, - "temperature": 100, - "solidify_temperature": 98, - "solidify": "water", - "explosive": false, - "explosion_radius": 0, - "explosion_color": [0, 0, 0], - "friction": 0.5, - "viscosity": 0.5, - "liquid": false, - "solid": false, - "is_gas": true, - "conductive": false - }, - "ice": { - "name": "Ice", - "size": 1, - "hardness": 1000, - "velocity": 0.0, - "conductivity": 0, - "heat_capacity": 0, - "color": [75, 75, 75, 255], - "mass": 1, - "flamability": 0.0, - "temperature": 0, - "explosive": false, - "explosion_radius": 0, - "explosion_color": [0, 0, 0], - "friction": 1, - "viscosity": 1, - "liquid": false, - "solid": true, - "is_gas": false - }, - "mud": { - "name": "Mud", - "size": 1, - "hardness": 0.4, - "velocity": 0.5, - "conductivity": 1, - "heat_capacity": 1, - "color": [139, 69, 19, 255], - "mass": 0.5, - "flamability": 0, - "temperature": 0, - "explosive": false, - "explosion_radius": 0, - "explosion_color": [0, 0, 0], - "friction": 0.5, - "viscosity": 1, - "liquid": false, - "solid": true, - "is_gas": false - }, - "fire": { - "name": "Fire", - "size": 1, - "hardness": 0.1, - "velocity": 0.1, - "conductivity": 0, - "heat_capacity": 1, - "color": [255, 0, 0, 255], - "mass": 0.1, - "flamability": 1, - "temperature": 1000, - "explosive": false, - "explosion_radius": 0, - "explosion_color": [0, 0, 0], - "friction": 0.1, - "viscosity": 0.1, - "liquid": false, - "solid": false, - "is_gas": true - }, - "smoke": { - "name": "Smoke", - "size": 1, - "hardness": 0.1, - "velocity": 0.1, - "conductivity": 0, - "heat_capacity": 1, - "color": [115, 113, 95, 255], - "mass": 0.01, - "flamability": 0, - "temperature": 85, - "explosive": false, - "explosion_radius": 0, - "explosion_color": [0, 0, 0], - "friction": 0.4, - "viscosity": 0.1, - "lifetime": 90, - "liquid": false, - "solid": false, - "is_gas": true - }, - "wall": { - "name": "Wall", - "size": 1, - "hardness": 1000, - "velocity": 0.0, - "conductivity": 0, - "heat_capacity": 0, - "color": [75, 75, 75, 255], - "mass": 1, - "flamability": 0.1, - "temperature": 0, - "explosive": false, - "explosion_radius": 0, - "explosion_color": [0, 0, 0], - "friction": 1, - "viscosity": 1, - "liquid": false, - "solid": true, - "is_gas": false - }, - "dirt": { - "name": "Dirt", - "size": 1, - "hardness": 0.5, - "velocity": 0.5, - "conductivity": 0, - "heat_capacity": 1, - "color": [139, 69, 19, 255], - "mass": 0.5, - "flamability": 0, - "temperature": 0, - "explosive": false, - "explosion_radius": 0, - "explosion_color": [0, 0, 0], - "friction": 0.5, - "viscosity": 0.5, - "liquid": false, - "solid": true, - "is_gas": false - }, - "stone": { - "name": "Stone", - "size": 1, - "hardness": 0.7, - "velocity": 1.5, - "conductivity": 0, - "heat_capacity": 0, - "color": [128, 128, 128, 255], - "mass": 1, - "flamability": 0, - "melt": "molten-Stone", - "melt_temperature": 800, - "solidify": "stone", - "solidify_temperature": 799, - "temperature": 0, - "explosive": false, - "explosion_radius": 0, - "explosion_color": [0, 0, 0], - "friction": 0.5, - "viscosity": 0.5, - "liquid": false, - "solid": true, - "is_gas": false - }, - "snow": { - "name": "Snow", - "size": 1, - "hardness": 0.1, - "velocity": 0.2, - "conductivity": 1, - "heat_capacity": 1, - "color": [255, 255, 255, 255], - "mass": 0.01, - "flamability": 0, - "melt": "water", - "melt_temperature": 10, - "temperature": 0, - "explosive": false, - "explosion_radius": 0, - "explosion_color": [0, 0, 0], - "friction": 0.1, - "viscosity": 0.01, - "liquid": false, - "solid": true, - "is_gas": false - }, - "wood": { - "name": "Wood", - "size": 1, - "hardness": 0.5, - "velocity": 0.5, - "conductivity": 0, - "heat_capacity": 1, - "color": [139, 69, 19, 255], - "mass": 0.5, - "flamability": 0.8, - "burning_temperature": 250, - "burning_rate": 0.01, - "burning_color": [255, 0, 0, 255], - "burning": false, - "temperature": 0, - "explosive": false, - "explosion_radius": 0, - "explosion_color": [0, 0, 0], - "friction": 0.5, - "viscosity": 0.5, - "liquid": false, - "solid": true, - "is_gas": false - }, - "burning-wood": { - "name": "Burning Wood", - "size": 1, - "hardness": 0.5, - "velocity": 0.5, - "conductivity": 0, - "heat_capacity": 1, - "color": [139, 69, 19, 255], - "mass": 0.5, - "flamability": 0.8, - "temperature": 0, - "explosive": false, - "explosion_radius": 0, - "explosion_color": [0, 0, 0], - "friction": 0.5, - "viscosity": 0.5, - "liquid": false, - "solid": true, - "is_gas": false - }, - "air": { - "name": "Air", - "size": 1, - "hardness": 0.0, - "velocity": 0.0, - "conductivity": 0, - "heat_capacity": 1, - "color": [25, 25, 25, 25], - "mass": 0.0, - "flamability": 0, - "temperature": 0, - "explosive": false, - "explosion_radius": 0, - "explosion_color": [0, 0, 0], - "friction": 0.0, - "viscosity": 0.0, - "liquid": false, - "solid": false, - "is_gas": true - }, - "lava": { - "name": "Lava", - "size": 1, - "hardness": 0.2, - "velocity": 0.5, - "conductivity": 0, - "heat_capacity": 1, - "color": [255, 45, 24, 255], - "mass": 0.3, - "flamability": 0, - "temperature": 2700, - "solidify": "molten-rock", - "solidify_temperature": 799, - "explosive": false, - "explosion_radius": 0, - "explosion_color": [0, 0, 0], - "friction": 0.8, - "viscosity": 0.8, - "liquid": true, - "solid": false, - "is_gas": false - }, + "sand": { + "name": "Sand", + "size": 1, + "hardness": 0.5, + "color": [255, 255, 0, 255], + "velocity": 0.5, + "mass": 0.5, + "conductivity": 0, + "heat_capacity": 1, + "flamability": 0.8, + "temperature": 0, + "explosive": false, + "explosion_radius": 0, + "explosion_color": [0, 0, 0], + "friction": 0.5, + "viscosity": 0, + "pressure": 0, + "melt": "molten-Glass", + "melt_temperature": 1700, + "conductive": false, + "liquid": false, + "solid": true, + "is_gas": false + }, + "water": { + "name": "Water", + "size": 1, + "hardness": 0.2, + "velocity": 0.3, + "conductivity": 1, + "heat_capacity": 1, + "color": [0, 0, 255, 255], + "mass": 1, + "flamability": 0, + "temperature": 22, + "explosive": false, + "explosion_radius": 0, + "explosion_color": [0, 0, 0], + "friction": 1, + "viscosity": 1, + "pressure": 0.5, + "evaporate": "steam", + "evaporate_temperature": 100, + "freeze": "ice", + "freeze_temperature": 0, + "conductive": true, + "liquid": true, + "solid": false, + "is_gas": false + }, + "steam": { + "name": "Steam", + "size": 1, + "hardness": 0.0, + "velocity": 0.3, + "conductivity": 1, + "heat_capacity": 1, + "color": [255, 255, 255, 255], + "mass": 0.01, + "flamability": 0, + "temperature": 100, + "solidify_temperature": 98, + "solidify": "water", + "explosive": false, + "explosion_radius": 0, + "explosion_color": [0, 0, 0], + "friction": 0.5, + "viscosity": 0.5, + "liquid": false, + "solid": false, + "is_gas": true, + "conductive": false + }, + "ice": { + "name": "Ice", + "size": 1, + "hardness": 1000, + "velocity": 0.0, + "conductivity": 0, + "heat_capacity": 0, + "color": [75, 75, 75, 255], + "mass": 1, + "flamability": 0.0, + "temperature": 0, + "melt": "water", + "melt_temperature": 0.05, + "explosive": false, + "explosion_radius": 0, + "explosion_color": [0, 0, 0], + "friction": 1, + "viscosity": 1, + "liquid": false, + "solid": true, + "is_gas": false + }, + "mud": { + "name": "Mud", + "size": 1, + "hardness": 0.4, + "velocity": 0.5, + "conductivity": 1, + "heat_capacity": 1, + "color": [139, 69, 19, 255], + "mass": 0.5, + "flamability": 0, + "temperature": 0, + "explosive": false, + "explosion_radius": 0, + "explosion_color": [0, 0, 0], + "friction": 0.5, + "viscosity": 1, + "liquid": false, + "solid": true, + "is_gas": false + }, + "fire": { + "name": "Fire", + "size": 1, + "hardness": 0.1, + "velocity": 0.1, + "conductivity": 0, + "heat_capacity": 1, + "color": [255, 0, 0, 255], + "mass": 0.1, + "flamability": 1, + "temperature": 800, + "explosive": false, + "explosion_radius": 0, + "explosion_color": [0, 0, 0], + "friction": 0.1, + "viscosity": 0.1, + "liquid": false, + "solid": false, + "is_gas": true + }, + "smoke": { + "name": "Smoke", + "size": 1, + "hardness": 0.1, + "velocity": 0.07, + "conductivity": 0, + "heat_capacity": 1, + "color": [115, 113, 95, 255], + "mass": 0.01, + "flamability": 0, + "temperature": 85, + "explosive": false, + "explosion_radius": 0, + "explosion_color": [0, 0, 0], + "friction": 0.4, + "viscosity": 0.1, + "lifetime": 90, + "liquid": false, + "solid": false, + "is_gas": true + }, + "wall": { + "name": "Wall", + "size": 1, + "hardness": 1000, + "velocity": 0.0, + "conductivity": 0, + "heat_capacity": 0, + "color": [75, 75, 75, 255], + "mass": 1, + "flamability": 0, + "temperature": 0, + "explosive": false, + "explosion_radius": 0, + "explosion_color": [0, 0, 0], + "friction": 1, + "viscosity": 1, + "liquid": false, + "solid": true, + "is_gas": false + }, + "dirt": { + "name": "Dirt", + "size": 1, + "hardness": 0.5, + "velocity": 0.5, + "conductivity": 0, + "heat_capacity": 1, + "color": [139, 69, 19, 255], + "mass": 0.5, + "flamability": 0, + "temperature": 0, + "explosive": false, + "explosion_radius": 0, + "explosion_color": [0, 0, 0], + "friction": 0.5, + "viscosity": 0.5, + "liquid": false, + "solid": true, + "is_gas": false + }, + "stone": { + "name": "Stone", + "size": 1, + "hardness": 0.7, + "velocity": 1.5, + "conductivity": 0, + "heat_capacity": 0, + "color": [128, 128, 128, 255], + "mass": 1, + "flamability": 0, + "melt": "molten-Stone", + "melt_temperature": 800, + "solidify": "stone", + "solidify_temperature": 799, + "temperature": 0, + "explosive": false, + "explosion_radius": 0, + "explosion_color": [0, 0, 0], + "friction": 0.5, + "viscosity": 0.5, + "liquid": false, + "solid": true, + "is_gas": false + }, + "snow": { + "name": "Snow", + "size": 1, + "hardness": 0.1, + "velocity": 0.2, + "conductivity": 1, + "heat_capacity": 1, + "color": [255, 255, 255, 255], + "mass": 0.01, + "flamability": 0, + "melt": "water", + "melt_temperature": 1, + "temperature": 0, + "explosive": false, + "explosion_radius": 0, + "explosion_color": [0, 0, 0], + "friction": 0.1, + "viscosity": 0.01, + "liquid": false, + "solid": true, + "is_gas": false + }, + "wood": { + "name": "Wood", + "size": 1, + "hardness": 0.5, + "velocity": 0.5, + "conductivity": 0, + "heat_capacity": 1, + "color": [139, 69, 19, 255], + "mass": 0.5, + "flamability": 0.8, + "burning_temperature": 250, + "burning_rate": 0.01, + "burning_color": [255, 0, 0, 255], + "burning": false, + "temperature": 0, + "explosive": false, + "explosion_radius": 0, + "explosion_color": [0, 0, 0], + "friction": 0.5, + "viscosity": 0.5, + "liquid": false, + "solid": true, + "is_gas": false + }, + "burning-wood": { + "name": "Burning Wood", + "size": 1, + "hardness": 0.5, + "velocity": 0.5, + "conductivity": 0, + "heat_capacity": 1, + "color": [139, 69, 19, 255], + "mass": 0.5, + "flamability": 1, + "temperature": 251, + "burning": true, + "explosive": false, + "explosion_radius": 0, + "explosion_color": [0, 0, 0], + "friction": 0.5, + "viscosity": 0.5, + "liquid": false, + "solid": true, + "is_gas": false + }, + "air": { + "name": "Air", + "size": 1, + "hardness": 0.0, + "velocity": 0.0, + "conductivity": 0, + "heat_capacity": 1, + "color": [25, 25, 25, 25], + "mass": 0.0, + "flamability": 0, + "temperature": 0, + "explosive": false, + "explosion_radius": 0, + "explosion_color": [0, 0, 0], + "friction": 0.0, + "viscosity": 0.0, + "liquid": false, + "solid": false, + "is_gas": true + }, + "lava": { + "name": "Lava", + "size": 1, + "hardness": 0.2, + "velocity": 0.5, + "conductivity": 0, + "heat_capacity": 1, + "color": [255, 45, 24, 255], + "mass": 0.3, + "flamability": 0, + "temperature": 1400, + "solidify": "molten-rock", + "solidify_temperature": 799, + "explosive": false, + "explosion_radius": 0, + "explosion_color": [0, 0, 0], + "friction": 0.8, + "viscosity": 0.8, + "liquid": true, + "solid": false, + "is_gas": false + }, "rock": { "name": "Rock", "size": 1, @@ -337,15 +339,11 @@ "mass": 0.8, "flamability": 0, "melt": "molten-rock", - "melt_temperature": 799, + "melt_temperature": 600, "temperature": 0, "explosive": false, "explosion_radius": 0, - "explosion_color": [ - 0, - 0, - 0 - ], + "explosion_color": [0, 0, 0], "friction": 0.5, "viscosity": 0.5, "liquid": false, @@ -362,9 +360,9 @@ "color": [255, 140, 0, 255], "mass": 0.8, "flamability": 0, - "temperature": 799, - "melt": "lavaa", - "melt_temperature": 1200, + "temperature": 600, + "melt": "lava", + "melt_temperature": 1300, "explosive": false, "explosion_radius": 0, "explosion_color": [0,0,0], @@ -376,70 +374,92 @@ "solidify": "rock", "solidify_temperature": 200 }, - "molten_stone": { - "name": "Molten Stone", - "size": 1, - "hardness": 0.2, - "velocity": 0.3, - "conductivity": 1, - "heat_capacity": 1, - "color": [255, 140, 0, 255], - "mass": 0.8, - "flamability": 0, - "temperature": 1200, - "explosive": false, - "explosion_radius": 0, - "explosion_color": [0, 0, 0], - "friction": 0.8, - "viscosity": 0.8, - "liquid": true, - "solid": false, - "is_gas": false, - "solidify": "stone", - "solidify_temperature": 800 - }, - "molten_glass": { - "name": "Molten Glass", - "size": 1, - "hardness": 0.2, - "velocity": 0.4, - "conductivity": 0.8, - "heat_capacity": 1, - "color": [255, 200, 150, 200], - "mass": 0.6, - "flamability": 0, - "temperature": 1200, - "explosive": false, - "explosion_radius": 0, - "explosion_color": [0, 0, 0], - "friction": 0.7, - "viscosity": 0.9, - "liquid": true, - "solid": false, - "is_gas": false, - "solidify": "glass", - "solidify_temperature": 800 - }, - "flame": { - "name": "Flame", - "size": 1, - "hardness": 0.0, - "velocity": 0.0, - "conductivity": 0, - "heat_capacity": 1, - "color": [255, 100, 0, 255], - "mass": 0.0, - "flamability": 0, - "temperature": 1000, - "explosive": false, - "explosion_radius": 0, - "explosion_color": [0, 0, 0], - "friction": 0.0, - "viscosity": 0.0, - "liquid": false, - "solid": false, - "is_gas": true - } + "molten_stone": { + "name": "Molten Stone", + "size": 1, + "hardness": 0.2, + "velocity": 0.3, + "conductivity": 1, + "heat_capacity": 1, + "color": [255, 140, 0, 255], + "mass": 0.8, + "flamability": 0, + "temperature": 1200, + "explosive": false, + "explosion_radius": 0, + "explosion_color": [0, 0, 0], + "friction": 0.8, + "viscosity": 0.8, + "liquid": true, + "solid": false, + "is_gas": false, + "solidify": "stone", + "solidify_temperature": 800 + }, + "molten_glass": { + "name": "Molten Glass", + "size": 1, + "hardness": 0.2, + "velocity": 0.4, + "conductivity": 0.8, + "heat_capacity": 1, + "color": [255, 200, 150, 200], + "mass": 0.6, + "flamability": 0, + "temperature": 600, + "explosive": false, + "explosion_radius": 0, + "explosion_color": [0, 0, 0], + "friction": 0.7, + "viscosity": 0.9, + "liquid": true, + "solid": false, + "is_gas": false, + "solidify": "glass", + "solidify_temperature": 599 + }, + "glass": { + "name": "Glass", + "size": 1, + "hardness": 0.2, + "velocity": 0.4, + "conductivity": 0, + "heat_capacity": 1, + "color": [50, 45, 255, 100], + "mass": 0.6, + "flamability": 0, + "temperature": 20, + "explosive": false, + "explosion_radius": 0, + "explosion_color": [0, 0, 0], + "friction": 0.7, + "viscosity": 0.9, + "liquid": false, + "solid": true, + "is_gas": false, + "melt": "molten-glass", + "melt_temperature": 1000 + }, + "flame": { + "name": "Flame", + "size": 1, + "hardness": 0.0, + "velocity": 0.0, + "conductivity": 0, + "heat_capacity": 1, + "color": [255, 100, 0, 255], + "mass": 0.0, + "flamability": 0, + "temperature": 1000, + "explosive": false, + "explosion_radius": 0, + "explosion_color": [0, 0, 0], + "friction": 0.0, + "viscosity": 0.0, + "liquid": false, + "solid": false, + "is_gas": true + } } diff --git a/rendering.py b/rendering.py index 1d9c9aa..94c4bbb 100644 --- a/rendering.py +++ b/rendering.py @@ -1,4 +1,26 @@ +""" #File Name: rendering.py +Rendering class for the particle simulation. + +This class is responsible for rendering the particles, UI elements, and debug information on the screen. It handles the setup of the display, pre-rendering of static UI elements, and the drawing of particles, buttons, and debug overlays. + +The `draw_particles` function is the main method for rendering the particles on the screen. It takes the particle data, active particles, particle size, and particle colors as input, and renders the particles on the `particle_surface`. The `particle_surface` is then blitted onto the main screen. + +The `draw_zoom_window` function is used to render a zoomed-in view of the particles around the mouse cursor. It creates a separate surface for the zoomed-in view and returns it. + +The `draw_debug_overlay` function is responsible for rendering the debug information, such as FPS, mouse position, and particle information, on the screen. + +The `draw_buttons` function handles the rendering of the category buttons, particle buttons, and other UI elements like the clear screen and settings buttons. + +The `render_brush_cursor` function is used to draw a visual indicator for the current brush size. + +The `draw_brush_size_slider` function renders a slider for adjusting the brush size. + +The `draw_settings_menu` function creates a settings menu surface that can be displayed on the screen. + +The `clear_screen` function is used to reset the simulation grid and clear the display surfaces. +""" + from settings import pygame, random, particle_properties, engine_settings @@ -11,14 +33,19 @@ class Rendering: self.background.fill((0, 0, 0)) self.width = width self.height = height - self.particle_surface = pygame.Surface((width, height), pygame.SRCALPHA) self.particle_colors = {} - self.button_width, self.button_height = 30, 30 self.particle_properties = particle_properties - self.buttons = {} - self.clear_screen_button = pygame.Rect(915, 10, 50, 30) - self.load_buttons() # Load buttons dynamically - self.particle_colors = {} + self.particle_surface = pygame.Surface((width, height), pygame.SRCALPHA) + self.debug_surface = pygame.Surface((300, 150), pygame.SRCALPHA) + self.cached_fonts = { + 'debug': pygame.font.SysFont(None, 24), + 'button': pygame.font.SysFont(None, 20) + } + + # Pre-render static UI elements + self.button_surfaces = {} + + # Initialize categories for name, properties in particle_properties.items(): if 'color' in properties: self.particle_colors[name.lower()] = properties['color'] @@ -36,7 +63,24 @@ class Rendering: self.current_category = 'Solids' self.category_buttons = {} self.setup_category_menu() - + self.setup_static_ui() + + + def setup_gpu_rendering(self): + # Initialize OpenGL context + pygame.display.gl_set_attribute(pygame.GL_ACCELERATED_VISUAL, 1) + self.screen = pygame.display.set_mode((self.width, self.height), + pygame.OPENGL | pygame.DOUBLEBUF) + + + def setup_static_ui(self): + for category in self.categories: + surf = pygame.Surface((80, 25)) + surf.fill((150, 150, 150)) + text = self.cached_fonts['button'].render(category, True, (0, 0, 0)) + surf.blit(text, (5, 5)) + self.button_surfaces[category] = surf + def setup_category_menu(self): # Category buttons at the top @@ -60,25 +104,27 @@ class Rendering: def draw_particles(self, particles, active_particles, particle_size, particle_colors): # this is the function that draws the particles - self.particle_surface = pygame.Surface((self.width, self.height), pygame.SRCALPHA) + #self.particle_surface = pygame.Surface((self.width, self.height), pygame.SRCALPHA) self.particle_surface.fill((0, 0, 0, 0)) + particle_batches = {} + for x, y in active_particles: particle = particles[x][y] if not particle: continue - - base_color = particle_colors.get(particle.particle_type, (255, 255, 255)) - color = list(base_color) - - if engine_settings['enable_glow']: - glow_color = (255, 255, 255) - glow_radius = 0.5 * particle_size - glow_surface = pygame.Surface((glow_radius * 2, glow_radius * 2), pygame.SRCALPHA) - pygame.draw.circle(glow_surface, glow_color, (glow_radius, glow_radius), glow_radius) - glow_surface.set_alpha(100) - self.particle_surface.blit(glow_surface, (x * particle_size - glow_radius, y * particle_size - glow_radius)) + p_type = particle.particle_type + if p_type not in particle_batches: + particle_batches[p_type] = [] + rect = pygame.Rect(x * particle_size, y * particle_size, + particle_size, particle_size) + particle_batches[p_type].append(rect) + if particle.particle_type in particle_colors: + color = particle_colors[particle.particle_type] + else: + color = (255, 255, 255) + if engine_settings['enable_gas_effect']: if particle.is_gas: # Enhanced gas visibility @@ -94,17 +140,38 @@ class Rendering: offset_y = random.randint(-1, 1) rect = (x * particle_size + offset_x, y * particle_size + offset_y, particle_size, particle_size) - - rect = (x * particle_size, y * particle_size, - particle_size, particle_size) + else: + rect = (x * particle_size, y * particle_size, + particle_size, particle_size) pygame.draw.rect(self.particle_surface, color, rect) + if engine_settings['enable_glow']: + glow_color = (255, 255, 255) + glow_radius = 0.5 * particle_size + glow_surface = pygame.Surface((glow_radius * 2, glow_radius * 2), pygame.SRCALPHA) + pygame.draw.circle(glow_surface, glow_color, (glow_radius, glow_radius), glow_radius) + glow_surface.set_alpha(85) + self.particle_surface.blit(glow_surface, (x * particle_size - glow_radius, y * particle_size - glow_radius)) + + + self.screen.blit(self.background, (0, 0)) self.screen.blit(self.particle_surface, (0, 0)) + """#Potentially for future + for p_type, rects in particle_batches.items(): + color = particle_colors.get(p_type, (255, 255, 255)) + rect = (x * particle_size, y * particle_size, + particle_size, particle_size) + if len(rects) > 1: + pygame.draw.rect(self.particle_surface, color, rect) + else: + pygame.draw.rect(self.particle_surface, color, rect) + self.screen.blit(self.background, (0, 0)) + self.screen.blit(self.particle_surface, (0, 0))""" - def draw_zoom_window(self, particles, particle_size, particle_colors, mouse_pos, zoom_factor=4): + def draw_zoom_window(self, particles, particle_size, particle_colors, mouse_pos, zoom_factor=4): # this is the function that draws the zoom window print(f"Drawing zoom window.") zoom_size = 100 # Size of zoom window zoom_surface = pygame.Surface((zoom_size, zoom_size)) @@ -132,101 +199,127 @@ class Rendering: return zoom_surface - def draw_debug_overlay(self, fps, particles): # this is the function that draws the debug overlay - # Get mouse position and convert to grid coordinates - mouse_x, mouse_y = pygame.mouse.get_pos() - grid_x = mouse_x // 3 - grid_y = mouse_y // 3 - - # Get particle info under cursor - particle_info = "None" - if 0 <= grid_x < len(particles) and 0 <= grid_y < len(particles[0]): - particle = particles[grid_x][grid_y] + def _get_particle_info(self, particles, x, y): + if 0 <= x < len(particles) and 0 <= y < len(particles[0]): + particle = particles[x][y] if particle: - # Include more detailed particle information - particle_info = f"Type: {particle.particle_type}" - if hasattr(particle, 'temperature'): - particle_info += f" | Temp: {particle.temperature}°C" - if hasattr(particle, 'liquid'): - particle_info += f" | Liquid: {particle.liquid}" - if hasattr(particle, 'is_gas'): - particle_info += f" | Gas: {particle.is_gas}" - if hasattr(particle, 'solid'): - particle_info += f" | Solid: {particle.solid}" - if hasattr(particle, 'mass'): - particle_info += f" | Mass: {particle.mass}" - if hasattr(particle, 'velocity'): - particle_info += f" | Velocity: {particle.velocity}" - if hasattr(particle, 'friction'): - particle_info += f" | Friction: {particle.friction}" + attrs = ['temperature', 'liquid', 'is_gas', 'solid', 'mass', 'velocity', 'friction'] + info = [f"Type: {particle.particle_type}"] + info.extend(f"{attr}: {getattr(particle, attr)}" for attr in attrs + if hasattr(particle, attr)) + return " | ".join(info) + return "None" - # Draw debug information - font = pygame.font.SysFont(None, 24) - particle_count = sum(1 for row in particles for cell in row if cell is not None) + + def draw_debug_overlay(self, fps, particles): # this is the function that draws the debug overlay + if engine_settings ['enable_fps']: + # Draw FPS + font = pygame.font.Font(None, 24) + fps_text = font.render(f"FPS: {fps:.2f}", True, (255, 255, 255)) + self.screen.blit(fps_text, (10, 10)) + + if engine_settings ['enable_debug']: + # Get mouse position and convert to grid coordinates + self.debug_surface.fill((0, 0, 0, 0)) + mouse_x, mouse_y = pygame.mouse.get_pos() + grid_x, grid_y = mouse_x // 3, mouse_y // 3 + + particle_info = self._get_particle_info(particles, grid_x, grid_y) + + # Draw debug information + font = pygame.font.SysFont(None, 24) + particle_count = sum(1 for row in particles for cell in row if cell is not None) + + debug_info = [ + f"FPS: {int(fps)}", + f"Mouse: ({mouse_x}, {mouse_y})", + f"Grid: ({grid_x}, {grid_y})", + f"Particle: {particle_info}", + #f"Active Particle Count: {sim.Simulation.get_active_particle_count(sim.Simulation)}", + f"Particle Count: {particle_count}" + ] + + y_offset = 10 + + for info in debug_info: + debug_text = font.render(info, True, (255, 255, 255)) + self.screen.blit(debug_text, (10, y_offset)) + y_offset += 25 + self.screen.blit(self.debug_surface, (10, 10)) - debug_info = [ - f"FPS: {int(fps)}", - f"Mouse: ({mouse_x}, {mouse_y})", - f"Grid: ({grid_x}, {grid_y})", - f"Particle: {particle_info}", - #f"Active Particle Count: {sim.Simulation.get_active_particle_count(sim.Simulation)}", - f"Particle Count: {particle_count}" - ] - y_offset = 10 - for info in debug_info: - debug_text = font.render(info, True, (255, 255, 255)) - self.screen.blit(debug_text, (10, y_offset)) - y_offset += 25 - - def draw_buttons(self): # this is the function that draws the buttons self.buttons = {} - # Draw category buttons vertically on right + # Draw category buttons on right x_offset = self.width - 100 y_offset = 10 + + # Draw cached category buttons for category, button in self.category_buttons.items(): - color = (200, 200, 200) if category == self.current_category else (150, 150, 150) - pygame.draw.rect(self.screen, color, button) - font = pygame.font.SysFont(None, 20) - label = font.render(category, True, (0, 0, 0)) - self.screen.blit(label, (button.x + 5, button.y + 5)) + surf = self.button_surfaces[category] + if category == self.current_category: + surf.set_alpha(255) + else: + surf.set_alpha(200) + self.screen.blit(surf, button) # Draw particle buttons for current category y_offset = 150 # Start particle buttons below categories for particle_type in self.categories[self.current_category]: if particle_type in self.particle_properties: - color = self.particle_properties[particle_type].get('color', (255, 255, 255)) button_rect = pygame.Rect(x_offset, y_offset, 80, 25) self.buttons[particle_type] = button_rect - pygame.draw.rect(self.screen, color, button_rect) - pygame.draw.rect(self.screen, (0, 0, 0), button_rect, 2) - - font = pygame.font.SysFont(None, 20) - label = font.render(particle_type, True, (0, 0, 0)) - self.screen.blit(label, (x_offset + 5, y_offset + 5)) + # Use cached button surface + if particle_type in self.button_surfaces: + self.screen.blit(self.button_surfaces[particle_type], button_rect) + else: + # Create and cache button surface if not exists + button_surface = pygame.Surface((80, 25)) + color = self.particle_properties[particle_type].get('color', (255, 255, 255)) + button_surface.fill(color) + font = pygame.font.SysFont(None, 20) + label = font.render(particle_type, True, (0, 0, 0)) + button_surface.blit(label, (5, 5)) + self.button_surfaces[particle_type] = button_surface + self.screen.blit(button_surface, button_rect) y_offset += 30 # Stack buttons vertically + final_y_offset = y_offset + 10 + # Draw clear screen button + if 'clear' not in self.button_surfaces: + clear_surface = pygame.Surface((80, 25)) + clear_surface.fill((255, 0, 0)) + font = pygame.font.SysFont(None, 24) + label = font.render("Clear", True, (255, 255, 255)) + clear_surface.blit(label, (5, 5)) + self.button_surfaces['clear'] = clear_surface + self.clear_screen_button = pygame.Rect(x_offset, y_offset + 10, 80, 25) - pygame.draw.rect(self.screen, (255, 0, 0), self.clear_screen_button) - font = pygame.font.SysFont(None, 24) - label = font.render("Clear", True, (255, 255, 255)) - self.screen.blit(label, (self.clear_screen_button.x + 5, self.clear_screen_button.y + 5)) + self.screen.blit(self.button_surfaces['clear'], self.clear_screen_button) - # Draw Settings menu directly below the buttons + # Draw Settings menu button + if 'settings' not in self.button_surfaces: + settings_surface = pygame.Surface((80, 25)) + settings_surface.fill((255, 255, 255)) + font = pygame.font.SysFont(None, 24) + label = font.render("Settings", True, (0, 0, 0)) + settings_surface.blit(label, (5, 5)) + self.button_surfaces['settings'] = settings_surface + self.settings_button = pygame.Rect(x_offset, y_offset + 50, 80, 25) - pygame.draw.rect(self.screen, (255, 255, 255), self.settings_button) - font = pygame.font.SysFont(None, 24) - label = font.render("Settings", True, (0, 0, 0)) - self.screen.blit(label, (self.settings_button.x + 5, self.settings_button.y + 5)) + self.screen.blit(self.button_surfaces['settings'], self.settings_button) - - def render_brush_curser(self, x, y, radius): # this is the function that draws the brush curser but isn't used yet so unkown if works - # Draw a circle cursor for brushsize - pygame.draw.circle(self.screen, (255, 255, 255), (x, y), radius) + def render_brush_cursor(self, x, y, radius): + if engine_settings ['enable_cursor']: + # Draw outline circle + pygame.draw.circle(self.screen, (255, 255, 255), (x, y), radius, 1) + # Draw slightly transparent fill + cursor_surface = pygame.Surface((radius*2, radius*2), pygame.SRCALPHA) + pygame.draw.circle(cursor_surface, (255, 255, 255, 55), (radius, radius), radius) + self.screen.blit(cursor_surface, (x-radius, y-radius)) def draw_brush_size_slider(self, brush_size): # this is the function that draws the brush size slider diff --git a/sandpypi.py b/sandpypi.py index 771a82e..d59b215 100644 --- a/sandpypi.py +++ b/sandpypi.py @@ -1,3 +1,4 @@ +""" #File Name: sandpypi.py # Sandpypi by Stanton. # Project name is a placeholder. @@ -5,6 +6,13 @@ # This is my most functional system for falling sand in python yet i took some things i learned in JS. # This needs further optimizations to core performance sections. +The main function to run the Sandpypi program. + +This function initializes the Pygame environment, creates the simulation and rendering objects, and enters the main event loop. +It handles user input events such as mouse clicks, mouse wheel scrolling, and keyboard presses. +It also updates the simulation, draws the particles, buttons, and other UI elements, and manages the settings menu. +The main loop runs at a target frame rate of 60 FPS, with the actual frame rate displayed in the debug overlay. +""" from settings import pygame, engine_settings from rendering import Rendering from sim import Simulation @@ -31,8 +39,8 @@ def main(): # Main function to run the program running = True while running: - fps = clock.get_fps() - dt = clock.tick(60) / 1000 + fps = clock.get_fps() # Get the current frame rate + dt = clock.tick(300) / 1000 # sets the target frame rate to 60 FPS keys = pygame.key.get_pressed() zoom_active = keys[pygame.K_z] @@ -41,6 +49,12 @@ def main(): # Main function to run the program for event in pygame.event.get(): if event.type == pygame.MOUSEBUTTONDOWN: mouse_pos = pygame.mouse.get_pos() + # Check if clicking in settings area when menu is visible + in_settings_area = False + if settings_visible: + settings_rect = pygame.Rect(rendering.width - 320, 100, 300, 400) + in_settings_area = settings_rect.collidepoint(mouse_pos) + if event.button == 4: # Mouse wheel up sim.brush_size = min(sim.brush_size + 1, sim.max_brush_size) elif event.button == 5: # Mouse wheel down @@ -48,45 +62,48 @@ def main(): # Main function to run the program elif event.button == 1: # Left click over_button = False + # Check settings button first + if rendering.settings_button.collidepoint(mouse_pos): + settings_visible = not settings_visible + over_button = True + if zoom_active: zoom_locked = not zoom_locked if zoom_locked: zoom_pos = mouse_pos - - # Check category buttons - for category, button in rendering.category_buttons.items(): - if button.collidepoint(mouse_pos): - rendering.current_category = category - over_button = True - break - - # Check particle buttons - if not over_button: - for particle_type, button in rendering.buttons.items(): - if button.collidepoint(mouse_pos): - sim.current_particle_type = particle_type - over_button = True - break - - # Check clear screen button - if rendering.clear_screen_button.collidepoint(mouse_pos): - rendering.clear_screen(sim) - over_button = True - if rendering.settings_button.collidepoint(event.pos): - settings_visible = not settings_visible - - elif settings_visible: - # Handle settings toggles - mouse_pos = pygame.mouse.get_pos() + # Handle settings menu interactions + elif settings_visible and in_settings_area: relative_y = mouse_pos[1] - settings_menu_y setting_index = relative_y // 30 if 0 <= setting_index < len(engine_settings): setting_name = list(engine_settings.keys())[setting_index] engine_settings[setting_name] = not engine_settings[setting_name] + over_button = True + elif not in_settings_area: + + # Check category buttons + for category, button in rendering.category_buttons.items(): + if button.collidepoint(mouse_pos): + rendering.current_category = category + over_button = True + break + + # Check particle buttons + if not over_button: + for particle_type, button in rendering.buttons.items(): + if button.collidepoint(mouse_pos): + sim.current_particle_type = particle_type + over_button = True + break + + # Check clear screen button + if rendering.clear_screen_button.collidepoint(mouse_pos): + rendering.clear_screen(sim) + over_button = True - if not over_button: + if not over_button and not in_settings_area: mouse_down_left = True elif event.button == 3: # Right click @@ -136,11 +153,6 @@ def main(): # Main function to run the program x, y = pygame.mouse.get_pos() sim.create_particle(x, y) - - sim.simulate_step(dt) - rendering.draw_particles(sim.particles, sim.active_particles, sim.particle_size, rendering.particle_colors) - rendering.draw_buttons() - rendering.draw_brush_size_slider(sim.brush_size) if zoom_active or zoom_locked: mouse_pos = zoom_pos if zoom_locked else pygame.mouse.get_pos() zoom_surface = rendering.draw_zoom_window(sim.particles, sim.particle_size, rendering.particle_colors, mouse_pos) @@ -149,11 +161,27 @@ def main(): # Main function to run the program zoom_y = 10 if mouse_pos[1] > height/2 else height - 110 screen.blit(zoom_surface, (zoom_x, zoom_y)) + # Draw Settings if settings_visible: settings_menu = rendering.draw_settings_menu() rendering.screen.blit(settings_menu, (rendering.width - 320, 100)) rendering.draw_debug_overlay(fps, sim.particles) pygame.display.flip() + + # Update and draw particles + sim.simulate_step(dt=0.016) + # Draw particles + rendering.draw_particles(sim.particles, sim.active_particles, sim.particle_size, rendering.particle_colors) + # Draw buttons + rendering.draw_buttons() + # Draw brush size slider + rendering.draw_brush_size_slider(sim.brush_size) + # Get current mouse position + mouse_x, mouse_y = pygame.mouse.get_pos() + # Draw brush cursor at mouse position + rendering.render_brush_cursor(mouse_x, mouse_y, sim.brush_size * sim.particle_size) + + pygame.quit() diff --git a/settings.py b/settings.py index d2d62ef..807d990 100644 --- a/settings.py +++ b/settings.py @@ -1,14 +1,29 @@ +""" #File Name: settings.py -# Global settings and impoorts for the project. + +Global settings and imports for the project. + +This module defines various settings for the game engine, such as enabling or disabling the cursor, glow effect, gas effect, debug mode, and FPS display. It also provides a function to load particle properties from a JSON file. + +The `engine_settings` dictionary contains the configurable settings for the game engine. These settings can be used to customize the behavior of the game. + +The `load_particle_properties()` function attempts to load particle properties from a 'particles.json' file. If the file is not found or the JSON data is invalid, it returns an empty dictionary. + +The `particle_properties` variable is initialized by calling `load_particle_properties()` when the module is imported. +""" + import pygame import json import random -import time +import numpy as np engine_settings = { - 'enable_glow': True, - 'enable_gas_effect': True + 'enable_cursor': True, + 'enable_glow': False, + 'enable_gas_effect': True, + 'enable_debug': False, + 'enable_fps': True # 'settings': True/False } diff --git a/setup.py b/setup.py deleted file mode 100644 index f4712d1..0000000 --- a/setup.py +++ /dev/null @@ -1,8 +0,0 @@ -#this be a wip maybe gonna move heavy work loads to cython. - -from setuptools import setup -from Cython.Build import cythonize - -setup( - ext_modules=cythonize("simulation_core.pyx"), -) diff --git a/sim.py b/sim.py index 9a393d8..968e5e7 100644 --- a/sim.py +++ b/sim.py @@ -1,7 +1,24 @@ +""" #File Name: sim.py +Particle-based Physics Simulation System +====================================== + +This module implements a 2D particle simulation with physics, interactions, and state changes. + +Key Components: +-------------- +1. Particle Class + - Handles individual particle properties and behaviors + - Supports multiple particle types (solid, liquid, gas) + - Manages temperature and state transitions + +2. Simulation Class + - Core simulation engine + - Manages particle creation, movement and interactions + - Handles physics calculations and spatial partitioning +""" #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 random, particle_properties # Load particle properties from json so we know what particles we got and how they should be simulated. @@ -57,6 +74,9 @@ class Simulation: # the main class of the simulation. def __init__(self, width, height, x=0, y=0): + self.dormant_particles = set() + self.particle_movement_counter = {} + self.DORMANT_THRESHOLD = 10 self.x = x self.y = y self.new_x = 0 @@ -100,15 +120,39 @@ class Simulation: def update_spatial_grid(self): # this is where we update the spatial grid. """Update spatial grid for optimized collision detection""" - if len(self.active_particles) > 100: # Threshold for rebuild - self.spatial_grid.clear() - for x, y in self.active_particles: - 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)) + if len(self.active_particles) > 100: + self.spatial_grid = {} + cell_lists = {} - + for x, y in self.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)) + + self.spatial_grid = {k: set(v) for k, v in cell_lists.items()} + + def _check_dormant_state(self, x, y, particle): + key = (x, y) + if particle.particle_type == 'wall': + self.dormant_particles.add(key) + return True + + if not hasattr(particle, 'last_position'): + particle.last_position = (x, y) + self.particle_movement_counter[key] = 0 + return False + + if particle.last_position == (x, y): + self.particle_movement_counter[key] = self.particle_movement_counter.get(key, 0) + 1 + if self.particle_movement_counter[key] >= self.DORMANT_THRESHOLD: + self.dormant_particles.add(key) + return True + else: + particle.last_position = (x, y) + self.particle_movement_counter[key] = 0 + self.dormant_particles.discard(key) + return False def handle_phase_transitions(self, particle, x, y): # this is where we handle all the phase transitions. """Handle all phase transitions for a particle""" @@ -214,12 +258,12 @@ class Simulation: particle = self.particles[x][y] if not particle: continue - if particle.temperature > 1100: + if particle.temperature > 100: # Transition to gas particle.is_gas = True - particle.temperature = 1100 + particle.temperature = 100 particle.velocity = [random.uniform(-1, 1), random.uniform(-1, 1)] - particle.temperature < 1100 + particle.temperature < 100 particle.is_gas = False @@ -228,8 +272,12 @@ class Simulation: 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) + if particle.is_gas: + fx += self.wind[0] * 0.5 + fy += self.wind[1] * 0.5 + else: + fx += self.wind[0] + fy += self.wind[1] # Apply drag force drag = particle.viscosity * -1 @@ -237,23 +285,25 @@ class Simulation: 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 + neighbors = self._get_quick_neighbors(x, y) + for nx, ny in neighbors: 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 + self._apply_neighbor_forces(particle, neighbor, fx, fy) + return fx, fy + + 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)]] + 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 ignite_particle(self, particle): # this is where we ignite the particle. """Handle ignition and burning of flammable particles.""" @@ -518,7 +568,7 @@ class Simulation: # handle gas particles if particle.is_gas: # Gas-specific movement - dx = random.uniform(-1, 1) + dx = random.uniform(2, -1) dy = random.uniform(-2, 0) # Bias upward new_x = int(x + dx) new_y = int(y + dy) @@ -540,7 +590,7 @@ class Simulation: if particle.liquid: # Enhanced liquid spreading - spread_chance = 0.8 + spread_chance = 0.5 if random.random() < spread_chance: dx = random.choice([-1, 1]) if (0 <= x + dx < self.width and @@ -606,14 +656,75 @@ class Simulation: count += 1 return count - def get_active_particle_count(self): - """Returns the number of active particles in the simulation for the Debug display and for performance analysis.""" - pass - + def _process_particle_batch(self, batch, dt): + updates = [] + new_active = set() + + # Filter out dormant particles from the batch + active_batch = [pos for pos in batch if pos not in self.dormant_particles] + + for x, y in active_batch: + particle = self.particles[x][y] + if not particle: + continue + + if particle.particle_type == 'wall': + new_active.add((x, y)) + continue + + # Check if particle should become dormant + if self._check_dormant_state(x, y, particle): + new_active.add((x, y)) + continue + + # physics calculations + fx, fy = self.calculate_forces(particle, x, y) + particle.velocity[0] += (fx / particle.mass) * dt + particle.velocity[1] += (fy / particle.mass) * dt + + 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: + updates.append((x, y, new_x, new_y, particle)) + new_active.add((new_x, new_y)) + # Wake up neighboring dormant particles + self._wake_neighbors(new_x, new_y) + else: + new_active.add((x, y)) + + # Apply updates and return new active set + for old_x, old_y, new_x, new_y, particle in updates: + self.particles[old_x][old_y] = None + self.particles[new_x][new_y] = particle + particle.position = (new_x, new_y) + + return new_active + + 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 def simulate_step(self, dt): """Run a single step of the simulation""" + active_list = list(self.active_particles) + batch_size = 1000 + + for i in range(0, len(active_list), batch_size): + batch = active_list[i:i + batch_size] + self._process_particle_batch(batch, dt) + + # Update spatial grid only when needed + if len(self.active_particles) > 100: + self.update_spatial_grid() + # Update particle positions and physics self.apply_gravity(dt) self.apply_physics(dt) @@ -624,5 +735,3 @@ class Simulation: self.burning() self.spread_fire() - # Update spatial grid - self.update_spatial_grid() \ No newline at end of file diff --git a/simulation_core.pyx b/simulation_core.pyx deleted file mode 100644 index 74d689b..0000000 --- a/simulation_core.pyx +++ /dev/null @@ -1,36 +0,0 @@ -# simulation_core.pyx -# Cython code for simulating the physics of the system - -cimport cython -from libc.math cimport sqrt - -cdef class CParticle: - cdef float x, y, vx, vy, mass, gravity - - def __init__(self, float x, float y, float vx, float vy, float mass, float gravity): - self.x = x - self.y = y - self.vx = vx - self.vy = vy - self.mass = mass - self.gravity = gravity - - cpdef void apply_gravity(self, float dt): - self.vy += self.gravity * dt - self.y += self.vy * dt - self.x += self.vx * dt - - -cdef class SimulationCore: - cdef list particles - - def __init__(self): - self.particles = [] - - cpdef void add_particle(self, CParticle particle): - self.particles.append(particle) - - cpdef void update(self, float dt): - cdef CParticle particle - for particle in self.particles: - particle.apply_gravity(dt) diff --git a/template_particles.json b/template_particles.json index 1e48019..67fe3bb 100644 --- a/template_particles.json +++ b/template_particles.json @@ -1,7 +1,8 @@ { "Template": { - "description": "This is a template for particles.", - "description2": "Remove this for your own particle mods to work" + "description": "Defines the properties and behavior of various particle types, including sand, water, and steam.", + "description2": "This template can be used as a starting point for creating custom particle mods.", + "description3": "Remove 'Template' for your own particle mods to work only particles like below will work." }, "sand": { "name": "Sand",