i've added so much and idek anymore

This commit is contained in:
Sebastian Cabrera 2025-08-10 16:50:33 -04:00
parent c120871293
commit 51ff181659
Signed by: okseby
GPG key ID: 2DDBFDEE356CF3DE
19 changed files with 1573 additions and 10 deletions

1
.gitignore vendored
View file

@ -42,6 +42,7 @@ Debug/
Release/
x64/
x86/
.cache/
# IDE and Editor files
.vscode/

View file

@ -7,13 +7,26 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Find SDL2
find_package(SDL2 REQUIRED)
# GLM - direct path approach for macOS/Homebrew
set(GLM_INCLUDE_DIR "/opt/homebrew/include")
if(EXISTS "${GLM_INCLUDE_DIR}/glm/glm.hpp")
message(STATUS "Found GLM at: ${GLM_INCLUDE_DIR}")
else()
message(FATAL_ERROR "GLM not found at ${GLM_INCLUDE_DIR}/glm/glm.hpp")
endif()
# Collect all source files
set(SOURCES
src/main.cpp
src/core/Application.cpp
src/core/InputHandler.cpp
src/window/Window.cpp
src/renderer/Renderer.cpp
src/events/EventManager.cpp
src/particles/Particle.cpp
src/particles/ParticleManager.cpp
src/particles/ParticleSystem.cpp
src/particles/ParticleFactory.cpp
)
# Add executable
@ -22,8 +35,17 @@ add_executable(${PROJECT_NAME} ${SOURCES})
# Link SDL2
target_link_libraries(${PROJECT_NAME} SDL2::SDL2)
# GLM is header-only, no linking needed
# target_link_libraries(${PROJECT_NAME} ${GLM_LIBRARIES})
# Include directories for SDL2
target_include_directories(${PROJECT_NAME} PRIVATE ${SDL2_INCLUDE_DIRS})
# Include directories for GLM - set globally
include_directories(${GLM_INCLUDE_DIR})
# Alternative approach: also set via CMAKE_CXX_FLAGS as fallback
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -I${GLM_INCLUDE_DIR}")
# Set include directories for our source files
target_include_directories(${PROJECT_NAME} PRIVATE src)

View file

@ -1,10 +1,22 @@
#include "Application.h"
#include "Config.h"
#include "../utils/Utils.h"
#include "../core/InputHandler.h"
#include "../particles/ParticleFactory.h"
#include <glm/glm.hpp>
#include <glm/vec2.hpp>
#include <iostream>
#include <cstdlib>
#include <ctime>
Application::Application(const std::string& title, int width, int height)
: initialized_(false) {
: initialized_(false)
, lastFrameTime_(0)
, showBoundaries_(Config::SHOW_BOUNDARIES)
, useMaterialRendering_(false) {
// Initialize random seed for particle offset
std::srand(static_cast<unsigned int>(std::time(nullptr)));
if (!initialize()) {
Utils::logError("Failed to initialize application");
@ -25,9 +37,22 @@ Application::Application(const std::string& title, int width, int height)
return;
}
// Create particle manager
particleManager_ = std::make_unique<ParticleManager>();
particleManager_->initialize(width, height);
// Create event manager
eventManager_ = std::make_unique<EventManager>();
// Create input handler
inputHandler_ = std::make_unique<InputHandler>();
// Create particle factory
particleFactory_ = std::make_unique<ParticleFactory>();
// Connect particle manager to event manager
eventManager_->setParticleManager(particleManager_.get());
// Register event callbacks
eventManager_->registerCallback(EventType::Quit,
[this](const EventData& event) { onQuit(event); });
@ -35,6 +60,23 @@ Application::Application(const std::string& title, int width, int height)
[this](const EventData& event) { onKeyDown(event); });
eventManager_->registerCallback(EventType::KeyUp,
[this](const EventData& event) { onKeyUp(event); });
eventManager_->registerCallback(EventType::MouseButtonDown,
[this](const EventData& event) { onMouseButtonDown(event); });
eventManager_->registerCallback(EventType::MouseButtonUp,
[this](const EventData& event) { onMouseButtonUp(event); });
eventManager_->registerCallback(EventType::MouseMove,
[this](const EventData& event) { onMouseMove(event); });
// Set up input handler callbacks
inputHandler_->setOnParticleCreateCallback(
[this](const glm::vec2& start, const glm::vec2& end) {
createParticleAt(static_cast<int>(start.x), static_cast<int>(start.y));
});
inputHandler_->setOnParticlePathCallback(
[this](const glm::vec2& start, const glm::vec2& end) {
createParticlesAlongPath(start, end);
});
initialized_ = true;
}
@ -65,9 +107,14 @@ int Application::run() {
}
Utils::log("Starting application...");
Utils::log("Press 'B' to toggle boundaries on/off");
Utils::log("Press 'M' to toggle material rendering");
Utils::log("Press 'ESC' to quit");
// Main application loop
while (isRunning()) {
Uint32 frameStart = SDL_GetTicks();
// Handle events
eventManager_->pollEvents();
@ -76,6 +123,14 @@ int Application::run() {
// Render frame
render();
// Performance optimization: frame rate limiting
Uint32 frameTime = SDL_GetTicks() - frameStart;
if (frameTime < TARGET_FRAME_TIME) {
SDL_Delay(TARGET_FRAME_TIME - frameTime);
}
lastFrameTime_ = frameTime;
}
Utils::log("Application finished");
@ -83,8 +138,15 @@ int Application::run() {
}
void Application::update() {
// Update game logic here
// This is where you would update game objects, physics, etc.
// Update particle systems
if (particleManager_) {
particleManager_->update(1.0f / 60.0f); // Assuming 60 FPS
}
// Update input handler
if (inputHandler_) {
inputHandler_->update();
}
}
void Application::render() {
@ -94,8 +156,36 @@ void Application::render() {
renderer_->setDrawColor(0, 0, 0, 255);
renderer_->clear();
// Render game objects here
// This is where you would draw sprites, shapes, etc.
// Render particles
if (particleManager_) {
auto defaultSystem = particleManager_->getDefaultParticleSystem();
if (defaultSystem) {
if (useMaterialRendering_) {
renderer_->renderParticlesWithMaterials(defaultSystem->getAllParticles(), window_->getWidth(), window_->getHeight());
} else {
renderer_->renderParticles(defaultSystem->getAllParticles(), window_->getWidth(), window_->getHeight());
}
}
}
// Render boundaries (floor and walls)
renderer_->drawBoundaries(window_->getWidth(), window_->getHeight(), showBoundaries_);
// Draw boundary status text (simple visual indicator)
if (showBoundaries_) {
// Draw a small indicator in the top-left corner
renderer_->setDrawColor(0, 255, 0, 255); // Green for enabled
SDL_Rect indicatorRect = {10, 10, 20, 20};
renderer_->fillRect(indicatorRect);
}
// Draw material rendering status indicator
if (useMaterialRendering_) {
// Draw a small indicator in the top-right corner
renderer_->setDrawColor(0, 255, 255, 255); // Cyan for material rendering
SDL_Rect indicatorRect = {window_->getWidth() - 30, 10, 20, 20};
renderer_->fillRect(indicatorRect);
}
// Present the frame
renderer_->present();
@ -112,6 +202,16 @@ void Application::onKeyDown(const EventData& event) {
case SDLK_ESCAPE:
stop();
break;
case SDLK_b:
// Toggle boundaries
showBoundaries_ = !showBoundaries_;
Utils::log("Boundaries " + std::string(showBoundaries_ ? "enabled" : "disabled"));
break;
case SDLK_m:
// Toggle material rendering
useMaterialRendering_ = !useMaterialRendering_;
Utils::log("Material rendering " + std::string(useMaterialRendering_ ? "enabled" : "disabled"));
break;
// Add more key handling here
}
}
@ -119,3 +219,63 @@ void Application::onKeyDown(const EventData& event) {
void Application::onKeyUp(const EventData& event) {
// Handle key up events here
}
void Application::onMouseButtonDown(const EventData& event) {
const SDL_MouseButtonEvent& mouseEvent = event.sdlEvent.button;
Utils::log("Mouse button down: " + std::to_string((int)mouseEvent.button) + " at (" + std::to_string(mouseEvent.x) + ", " + std::to_string(mouseEvent.y) + ")");
if (mouseEvent.button == SDL_BUTTON_LEFT && inputHandler_) {
inputHandler_->startDrawing(glm::vec2(mouseEvent.x, mouseEvent.y));
}
}
void Application::onMouseButtonUp(const EventData& event) {
const SDL_MouseButtonEvent& mouseEvent = event.sdlEvent.button;
if (mouseEvent.button == SDL_BUTTON_LEFT && inputHandler_) {
inputHandler_->stopDrawing();
}
}
void Application::onMouseMove(const EventData& event) {
const SDL_MouseMotionEvent& mouseEvent = event.sdlEvent.motion;
if (inputHandler_) {
inputHandler_->updateMousePosition(glm::vec2(mouseEvent.x, mouseEvent.y));
}
}
void Application::createParticleAt(int x, int y) {
if (!particleManager_ || !particleFactory_) {
Utils::logError("ParticleManager or ParticleFactory is null!");
return;
}
Particle particle = particleFactory_->createSandParticle(glm::vec2(x, y));
particleManager_->addParticleToDefault(particle);
// Performance optimization: only log every 10th particle to reduce console spam
static int particleCount = 0;
if (++particleCount % 10 == 0) {
Utils::log("Added particle to default system at (" + std::to_string(x) + ", " + std::to_string(y) + ")");
}
}
void Application::createParticlesAlongPath(const glm::vec2& start, const glm::vec2& end) {
if (!particleManager_ || !particleFactory_) return;
std::vector<Particle> particles = particleFactory_->createParticlesAlongPath(start, end);
// Performance optimization: only process particles if there are any
if (!particles.empty()) {
for (const auto& particle : particles) {
particleManager_->addParticleToDefault(particle);
}
// Performance optimization: only log if we created a significant number of particles
if (particles.size() > 5) {
Utils::log("Created " + std::to_string(particles.size()) + " particles along path");
}
}
}

View file

@ -4,6 +4,9 @@
#include "../window/Window.h"
#include "../renderer/Renderer.h"
#include "../events/EventManager.h"
#include "../particles/ParticleManager.h"
#include "../core/InputHandler.h"
#include "../particles/ParticleFactory.h"
class Application {
public:
@ -31,9 +34,18 @@ private:
WindowPtr window_;
RendererPtr renderer_;
EventManagerPtr eventManager_;
std::unique_ptr<ParticleManager> particleManager_;
std::unique_ptr<InputHandler> inputHandler_;
std::unique_ptr<ParticleFactory> particleFactory_;
// Application state
bool initialized_;
bool showBoundaries_;
bool useMaterialRendering_;
// Performance optimization: frame rate limiting
static constexpr Uint32 TARGET_FRAME_TIME = 16; // ~60 FPS (1000ms / 60fps ≈ 16.67ms)
Uint32 lastFrameTime_;
// Private methods
bool initialize();
@ -41,8 +53,15 @@ private:
void update();
void render();
// Particle management
void createParticleAt(int x, int y);
void createParticlesAlongPath(const glm::vec2& start, const glm::vec2& end);
// Event callbacks
void onQuit(const EventData& event);
void onKeyDown(const EventData& event);
void onKeyUp(const EventData& event);
void onMouseButtonDown(const EventData& event);
void onMouseButtonUp(const EventData& event);
void onMouseMove(const EventData& event);
};

View file

@ -1,7 +1,5 @@
#pragma once
#include <string>
namespace Config {
// Window settings
constexpr int DEFAULT_WINDOW_WIDTH = 800;
@ -12,6 +10,7 @@ namespace Config {
constexpr bool VSYNC_ENABLED = true;
constexpr int TARGET_FPS = 60;
constexpr float TARGET_FRAME_TIME = 1.0f / TARGET_FPS;
constexpr bool SHOW_BOUNDARIES = true;
// Application settings
constexpr bool DEBUG_MODE = true;

94
src/core/InputHandler.cpp Normal file
View file

@ -0,0 +1,94 @@
#include "InputHandler.h"
#include "../utils/Utils.h"
InputHandler::InputHandler()
: state_(InputState::IDLE)
, lastMousePos_(0.0f, 0.0f)
, currentMousePos_(0.0f, 0.0f)
, lastParticleTime_(0) {
}
void InputHandler::update() {
// Input state updates can go here if needed
}
void InputHandler::startDrawing(const glm::vec2& position) {
state_ = InputState::DRAWING;
lastMousePos_ = position;
currentMousePos_ = position;
lastParticleTime_ = SDL_GetTicks();
// Only log once when starting to draw (not on every mouse move)
Utils::log("Started drawing particles at (" + std::to_string(position.x) + ", " + std::to_string(position.y) + ")");
// Trigger particle creation callback
if (onParticleCreate_) {
onParticleCreate_(position, position);
}
}
void InputHandler::stopDrawing() {
state_ = InputState::IDLE;
Utils::log("Stopped drawing particles");
}
void InputHandler::updateMousePosition(const glm::vec2& position) {
if (state_ == InputState::DRAWING) {
// Performance optimization: only create particles if conditions are met
if (shouldCreateParticle(position)) {
// Trigger particle path callback for continuous drawing
if (onParticlePath_) {
onParticlePath_(lastMousePos_, position);
}
lastMousePos_ = currentMousePos_;
lastParticleTime_ = SDL_GetTicks();
}
currentMousePos_ = position;
}
}
bool InputHandler::shouldCreateParticle(const glm::vec2& newPos) const {
// Check distance threshold
float distance = glm::length(newPos - lastMousePos_);
if (distance < MIN_PARTICLE_DISTANCE) {
return false;
}
// Check rate limiting
Uint32 currentTime = SDL_GetTicks();
if (currentTime - lastParticleTime_ < PARTICLE_CREATION_INTERVAL) {
return false;
}
return true;
}
void InputHandler::setOnParticleCreateCallback(MouseCallback callback) {
onParticleCreate_ = callback;
}
void InputHandler::setOnParticlePathCallback(MouseCallback callback) {
onParticlePath_ = callback;
}
void InputHandler::handleMouseButtonDown(const SDL_MouseButtonEvent& event) {
if (event.button == SDL_BUTTON_LEFT) {
glm::vec2 position(event.x, event.y);
startDrawing(position);
}
}
void InputHandler::handleMouseButtonUp(const SDL_MouseButtonEvent& event) {
if (event.button == SDL_BUTTON_LEFT) {
stopDrawing();
}
}
void InputHandler::handleMouseMove(const SDL_MouseMotionEvent& event) {
if (state_ == InputState::DRAWING) {
glm::vec2 position(event.x, event.y);
updateMousePosition(position);
}
}

63
src/core/InputHandler.h Normal file
View file

@ -0,0 +1,63 @@
#pragma once
#include <glm/glm.hpp>
#include <SDL2/SDL.h>
#include <functional>
enum class InputState {
IDLE,
DRAWING
};
class InputHandler {
public:
InputHandler();
~InputHandler() = default;
// Disable copying
InputHandler(const InputHandler&) = delete;
InputHandler& operator=(const InputHandler&) = delete;
// Input state management
void update();
InputState getState() const { return state_; }
bool isDrawing() const { return state_ == InputState::DRAWING; }
// Mouse position tracking
glm::vec2 getLastMousePos() const { return lastMousePos_; }
glm::vec2 getCurrentMousePos() const { return currentMousePos_; }
// Drawing state management
void startDrawing(const glm::vec2& position);
void stopDrawing();
void updateMousePosition(const glm::vec2& position);
// Callback registration
using MouseCallback = std::function<void(const glm::vec2&, const glm::vec2&)>;
void setOnParticleCreateCallback(MouseCallback callback);
void setOnParticlePathCallback(MouseCallback callback);
private:
InputState state_;
glm::vec2 lastMousePos_;
glm::vec2 currentMousePos_;
// Performance optimization: minimum distance threshold for particle creation
static constexpr float MIN_PARTICLE_DISTANCE = 8.0f; // Only create particles if mouse moved 8+ pixels
// Performance optimization: rate limiting for particle creation
Uint32 lastParticleTime_;
static constexpr Uint32 PARTICLE_CREATION_INTERVAL = 16; // ~60 particles per second max
// Callbacks
MouseCallback onParticleCreate_;
MouseCallback onParticlePath_;
// Helper methods
void handleMouseButtonDown(const SDL_MouseButtonEvent& event);
void handleMouseButtonUp(const SDL_MouseButtonEvent& event);
void handleMouseMove(const SDL_MouseMotionEvent& event);
// Performance helpers
bool shouldCreateParticle(const glm::vec2& newPos) const;
};

View file

@ -1,7 +1,13 @@
#include "EventManager.h"
#include <iostream>
#include "../particles/ParticleManager.h"
#include "../particles/Particle.h"
#include "../utils/Utils.h"
EventManager::EventManager() : running_(true) {
EventManager::EventManager() : running_(true), particleManager_(nullptr) {
}
void EventManager::setParticleManager(ParticleManager* manager) {
particleManager_ = manager;
}
bool EventManager::pollEvents() {
@ -91,3 +97,32 @@ void EventManager::handleWindowEvent(const SDL_Event& event) {
it->second(data);
}
}
void EventManager::onMouseButtonDown(const EventData& event) {
Utils::log("Mouse button down");
if (particleManager_ && event.sdlEvent.type == SDL_MOUSEBUTTONDOWN) {
const SDL_MouseButtonEvent& mouseEvent = event.sdlEvent.button;
int x = mouseEvent.x;
int y = mouseEvent.y;
// Create a sand particle at mouse position
Particle particle(Particle::MaterialType::SAND, glm::vec2(x, y));
particle.velocity = glm::vec2(0.0f, 0.0f);
particle.size = 2.0f;
particle.active = true;
// Add particle to the default system
particleManager_->addParticleToDefault(particle);
Utils::log("Created sand particle at (" + std::to_string(x) + ", " + std::to_string(y) + ")");
}
}
void EventManager::onMouseButtonUp(const EventData& event) {
Utils::log("Mouse button up");
}
void EventManager::onMouseMove(const EventData& event) {
Utils::log("Mouse move");
}

View file

@ -4,6 +4,8 @@
#include <functional>
#include <unordered_map>
class ParticleManager;
enum class EventType {
Quit,
KeyDown,
@ -33,6 +35,9 @@ public:
using EventCallback = std::function<void(const EventData&)>;
void registerCallback(EventType type, EventCallback callback);
void unregisterCallback(EventType type);
// Particle management
void setParticleManager(ParticleManager* manager);
// Event processing
void processEvent(const SDL_Event& event);
@ -40,9 +45,12 @@ public:
private:
bool running_;
std::unordered_map<EventType, EventCallback> callbacks_;
ParticleManager* particleManager_;
void handleQuitEvent();
void handleKeyEvent(const SDL_Event& event);
void handleMouseEvent(const SDL_Event& event);
void handleWindowEvent(const SDL_Event& event);
void onMouseButtonDown(const EventData& event);
void onMouseButtonUp(const EventData& event);
void onMouseMove(const EventData& event);
};

View file

@ -0,0 +1,71 @@
#include "Particle.h"
// Particle class implementation - define particle methods here
Particle::Particle()
: position(0.0f, 0.0f)
, velocity(0.0f, 0.0f)
, acceleration(0.0f, 0.0f)
, mass(1.0f)
, material(MaterialType::SAND)
, hardness(1.0f)
, cohesion(0.0f)
, friction(0.5f)
, size(1.0f)
, density(1.0f)
, temperature(20.0f)
, viscosity(0.0f)
, state(State::FALLING)
, active(true)
, gravity(9.8f)
, canFall(true)
, canFlow(false) {}
Particle::Particle(MaterialType mat, const glm::vec2& pos)
: Particle() {
material = mat;
position = pos;
setMaterial(mat);
}
void Particle::setMaterial(MaterialType mat) {
material = mat;
switch (mat) {
case MaterialType::SAND:
density = 1.6f;
canFall = true;
canFlow = false;
break;
case MaterialType::WATER:
density = 1.0f;
canFall = true;
canFlow = true;
break;
case MaterialType::ROCK:
density = 2.7f;
canFall = false;
canFlow = false;
break;
case MaterialType::ICE:
density = 0.9f;
canFall = true;
canFlow = false;
break;
case MaterialType::METAL:
density = 7.8f;
canFall = false;
canFlow = false;
break;
case MaterialType::EMPTY:
density = 0.0f;
canFall = false;
canFlow = false;
break;
default:
density = 1.0f;
canFall = true;
canFlow = false;
break;
}
}

60
src/particles/Particle.h Normal file
View file

@ -0,0 +1,60 @@
#pragma once
#include <glm/glm.hpp>
#include <glm/vec2.hpp>
// Particle class header - define individual particle properties and behavior here
struct Particle {
// Physical properties
glm::vec2 position;
glm::vec2 velocity;
glm::vec2 acceleration;
float mass;
// Material properties
enum class MaterialType {
SAND,
WATER,
ROCK,
ICE,
METAL,
EMPTY,
};
// Particle state
enum class State {
FALLING,
SETTLED,
FLOWING,
GAS,
};
// Material properties
MaterialType material;
float hardness;
float cohesion;
float friction;
// Particle properties
float size;
float density;
float temperature;
float viscosity;
// Particle state
State state;
bool active;
// Particle behavior
float gravity;
bool canFall;
bool canFlow;
// Particle constructor
Particle();
Particle(MaterialType mat, const glm::vec2& pos = {0.0f, 0.0f});
// Particle methods
void setMaterial(MaterialType mat);
};

View file

@ -0,0 +1,80 @@
#include "ParticleFactory.h"
#include <cmath>
#include <algorithm>
ParticleFactory::ParticleFactory()
: particleSize_(2.0f)
, offsetRange_(3.0f) { // Reduced from 5.0f for better performance
}
Particle ParticleFactory::createParticle(Particle::MaterialType material, const glm::vec2& position) {
Particle particle(material, position);
particle.velocity = glm::vec2(0.0f, 0.0f);
particle.size = particleSize_;
particle.active = true;
// Add small random offset for natural placement
glm::vec2 offset = generateRandomOffset();
particle.position += offset;
return particle;
}
Particle ParticleFactory::createSandParticle(const glm::vec2& position) {
return createParticle(Particle::MaterialType::SAND, position);
}
std::vector<Particle> ParticleFactory::createParticlesAlongPath(const glm::vec2& start, const glm::vec2& end, float spacing) {
std::vector<Particle> particles;
// Performance optimization: don't create particles for very short paths
float distance = glm::length(end - start);
if (distance < MIN_PATH_DISTANCE) {
return particles; // Return empty vector for very short paths
}
// Create particles at regular intervals along the path
int numParticles = static_cast<int>(distance / spacing);
// Performance optimization: limit maximum particles per path
if (numParticles > 0 && numParticles <= 20) { // Cap at 20 particles per path
glm::vec2 direction = glm::normalize(end - start);
float step = distance / numParticles;
for (int i = 1; i <= numParticles; ++i) {
glm::vec2 pos = start + direction * (step * i);
particles.push_back(createSandParticle(pos));
}
}
return particles;
}
std::vector<Particle> ParticleFactory::createParticlesInArea(const glm::vec2& center, float radius, int count) {
std::vector<Particle> particles;
// Performance optimization: limit maximum particles per area
count = std::min(count, 50); // Cap at 50 particles per area
for (int i = 0; i < count; ++i) {
float angle = randomFloat(0.0f, 2.0f * M_PI);
float distance = randomFloat(0.0f, radius);
glm::vec2 offset(cos(angle) * distance, sin(angle) * distance);
glm::vec2 position = center + offset;
particles.push_back(createSandParticle(position));
}
return particles;
}
glm::vec2 ParticleFactory::generateRandomOffset() {
float offsetX = (randomFloat(0.0f, 1.0f) - 0.5f) * offsetRange_;
float offsetY = (randomFloat(0.0f, 1.0f) - 0.5f) * offsetRange_;
return glm::vec2(offsetX, offsetY);
}
float ParticleFactory::randomFloat(float min, float max) {
return min + (max - min) * (static_cast<float>(rand()) / RAND_MAX);
}

View file

@ -0,0 +1,41 @@
#pragma once
#include "Particle.h"
#include <glm/glm.hpp>
#include <cstdlib>
#include <vector>
class ParticleFactory {
public:
ParticleFactory();
~ParticleFactory() = default;
// Disable copying
ParticleFactory(const ParticleFactory&) = delete;
ParticleFactory& operator=(const ParticleFactory&) = delete;
// Core particle creation methods
Particle createParticle(Particle::MaterialType material, const glm::vec2& position);
Particle createSandParticle(const glm::vec2& position);
// Batch particle creation
std::vector<Particle> createParticlesAlongPath(const glm::vec2& start, const glm::vec2& end, float spacing = 8.0f);
std::vector<Particle> createParticlesInArea(const glm::vec2& center, float radius, int count);
// Configuration
void setParticleSize(float size) { particleSize_ = size; }
void setOffsetRange(float range) { offsetRange_ = range; }
float getParticleSize() const { return particleSize_; }
float getOffsetRange() const { return offsetRange_; }
private:
float particleSize_;
float offsetRange_;
// Performance optimization: minimum distance threshold
static constexpr float MIN_PATH_DISTANCE = 4.0f; // Don't create particles for very short paths
// Helper methods
glm::vec2 generateRandomOffset();
float randomFloat(float min, float max);
};

View file

@ -0,0 +1,366 @@
#include "ParticleManager.h"
#include "core/Config.h"
#include "../utils/Utils.h"
#include <iostream>
#include <algorithm>
// ParticleManager class implementation - define manager methods here
ParticleManager::ParticleManager()
: initialized_(false)
, globalPaused_(false)
, globalSimulationSpeed_(1.0f)
, defaultWidth_(800)
, defaultHeight_(600) {
}
void ParticleManager::initialize(int defaultWidth, int defaultHeight) {
if (initialized_) {
return;
}
defaultWidth_ = defaultWidth;
defaultHeight_ = defaultHeight;
// Create default particle system
defaultSystem_ = createParticleSystem(defaultWidth_, defaultHeight_, "default");
initialized_ = true;
logSystemOperation("initialized");
}
void ParticleManager::update(float deltaTime) {
if (!initialized_ || globalPaused_) {
return;
}
// Update all particle systems
for (auto& [name, system] : systems_) {
if (system && !globalPaused_) {
updateSystem(name, deltaTime * globalSimulationSpeed_);
}
}
}
void ParticleManager::cleanup() {
if (!initialized_) {
return;
}
// Clear all systems
clearAllSystems();
// Reset state
initialized_ = false;
globalPaused_ = false;
globalSimulationSpeed_ = 1.0f;
logSystemOperation("cleaned up");
}
std::shared_ptr<ParticleSystem> ParticleManager::createParticleSystem(int width, int height, const std::string& name) {
std::string systemName = name.empty() ? generateUniqueSystemName("system") : name;
if (!validateSystemName(systemName)) {
std::cerr << "Invalid system name: " << systemName << std::endl;
return nullptr;
}
if (systems_.find(systemName) != systems_.end()) {
std::cerr << "System with name '" << systemName << "' already exists" << std::endl;
return nullptr;
}
auto system = std::make_shared<ParticleSystem>(width, height);
systems_[systemName] = system;
logSystemOperation("created", systemName);
return system;
}
void ParticleManager::destroyParticleSystem(const std::string& name) {
if (name == "default") {
std::cerr << "Cannot destroy default system" << std::endl;
return;
}
auto it = systems_.find(name);
if (it != systems_.end()) {
logSystemOperation("destroyed", name);
systems_.erase(it);
}
}
std::shared_ptr<ParticleSystem> ParticleManager::getParticleSystem(const std::string& name) {
auto it = systems_.find(name);
return (it != systems_.end()) ? it->second : nullptr;
}
std::shared_ptr<ParticleSystem> ParticleManager::getDefaultParticleSystem() {
return defaultSystem_;
}
void ParticleManager::addParticleToSystem(const std::string& systemName, const Particle& particle) {
auto system = getParticleSystem(systemName);
if (system) {
system->addParticle(particle);
}
}
void ParticleManager::addParticleToDefault(const Particle& particle) {
if (defaultSystem_) {
defaultSystem_->addParticle(particle);
}
}
void ParticleManager::clearAllSystems() {
for (auto& [name, system] : systems_) {
if (system) {
// Clear particles but keep the system structure
// This would need a clear method in ParticleSystem
}
}
logSystemOperation("cleared all systems");
}
void ParticleManager::resetAllSystems() {
// Destroy all non-default systems
std::vector<std::string> systemNames;
for (const auto& [name, _] : systems_) {
if (name != "default") {
systemNames.push_back(name);
}
}
for (const auto& name : systemNames) {
destroyParticleSystem(name);
}
// Reset default system
if (defaultSystem_) {
// This would need a reset method in ParticleSystem
}
logSystemOperation("reset all systems");
}
void ParticleManager::placeMaterialInSystem(const std::string& systemName, int x, int y, Particle::MaterialType material) {
auto system = getParticleSystem(systemName);
if (system) {
system->placeMaterial(x, y, material);
}
}
void ParticleManager::placeMaterialInDefault(int x, int y, Particle::MaterialType material) {
if (defaultSystem_) {
defaultSystem_->placeMaterial(x, y, material);
}
}
void ParticleManager::floodFill(const std::string& systemName, int startX, int startY, Particle::MaterialType material) {
auto system = getParticleSystem(systemName);
if (!system) {
return;
}
// Simple flood fill implementation
if (!system->isValidPosition(startX, startY) || !system->isEmpty(startX, startY)) {
return;
}
// This would need a flood fill implementation in ParticleSystem
// For now, just place material at the starting position
system->placeMaterial(startX, startY, material);
}
void ParticleManager::pauseSimulation(const std::string& systemName) {
if (systemName.empty()) {
globalPaused_ = true;
logSystemOperation("paused globally");
} else {
// Individual system pause would need pause state in ParticleSystem
logSystemOperation("paused", systemName);
}
}
void ParticleManager::resumeSimulation(const std::string& systemName) {
if (systemName.empty()) {
globalPaused_ = false;
logSystemOperation("resumed globally");
} else {
// Individual system resume would need pause state in ParticleSystem
logSystemOperation("resumed", systemName);
}
}
void ParticleManager::setSimulationSpeed(float speed, const std::string& systemName) {
if (systemName.empty()) {
globalSimulationSpeed_ = std::max(0.0f, speed);
logSystemOperation("set global speed to " + std::to_string(globalSimulationSpeed_));
} else {
// Individual system speed would need speed state in ParticleSystem
logSystemOperation("set speed for " + systemName + " to " + std::to_string(speed));
}
}
bool ParticleManager::isSimulationPaused(const std::string& systemName) const {
if (systemName.empty()) {
return globalPaused_;
}
// Individual system pause state would need pause state in ParticleSystem
return false;
}
int ParticleManager::getTotalParticleCount() const {
int total = 0;
for (const auto& [name, system] : systems_) {
if (system) {
total += system->getParticleCount();
}
}
return total;
}
int ParticleManager::getSystemCount() const {
return static_cast<int>(systems_.size());
}
std::vector<std::string> ParticleManager::getSystemNames() const {
std::vector<std::string> names;
names.reserve(systems_.size());
for (const auto& [name, _] : systems_) {
names.push_back(name);
}
return names;
}
void ParticleManager::printSystemStats() const {
Utils::log("\n=== Particle Manager Statistics ===");
Utils::log("Total Systems: " + std::to_string(getSystemCount()));
Utils::log("Total Particles: " + std::to_string(getTotalParticleCount()));
Utils::log("Global Paused: " + std::string(globalPaused_ ? "Yes" : "No"));
Utils::log("Global Speed: " + std::to_string(globalSimulationSpeed_) + "x");
for (const auto& [name, system] : systems_) {
if (system) {
Utils::log("\nSystem: " + name);
Utils::log(" Dimensions: " + std::to_string(system->getWidth()) + "x" + std::to_string(system->getHeight()));
Utils::log(" Particles: " + std::to_string(system->getParticleCount()));
}
}
Utils::log("================================");
}
bool ParticleManager::saveSystemToFile(const std::string& systemName, const std::string& filename) {
auto system = getParticleSystem(systemName);
if (!system) {
return false;
}
// This would need serialization methods in ParticleSystem
// For now, just log the operation
logSystemOperation("save requested to " + filename, systemName);
return true;
}
bool ParticleManager::loadSystemFromFile(const std::string& systemName, const std::string& filename) {
auto system = getParticleSystem(systemName);
if (!system) {
return false;
}
// This would need deserialization methods in ParticleSystem
// For now, just log the operation
logSystemOperation("load requested from " + filename, systemName);
return true;
}
bool ParticleManager::exportSystemsAsImage(const std::string& systemName, const std::string& filename) {
auto system = getParticleSystem(systemName);
if (!system) {
return false;
}
// This would need image export methods in ParticleSystem
// For now, just log the operation
logSystemOperation("export requested to " + filename, systemName);
return true;
}
// Private helper methods
void ParticleManager::updateSystem(const std::string& name, float deltaTime) {
auto system = getParticleSystem(name);
if (system) {
system->update(deltaTime);
}
}
bool ParticleManager::validateSystemName(const std::string& name) const {
return !name.empty() && name.length() <= 64 &&
name.find_first_of("\\/:*?\"<>|") == std::string::npos;
}
std::string ParticleManager::generateUniqueSystemName(const std::string& baseName) const {
static int counter = 0;
std::string name;
do {
name = "system_" + std::to_string(counter++);
} while (systems_.find(name) != systems_.end());
return name;
}
void ParticleManager::logSystemOperation(const std::string& operation, const std::string& systemName) {
if (Config::LOGGING_ENABLED) {
std::string message = "[ParticleManager] " + operation;
if (!systemName.empty()) {
message += " (" + systemName + ")";
}
Utils::log(message);
}
}
void ParticleManager::applyMaterialPhysics(Particle& particle, Particle::MaterialType material) {
// Apply material-specific physics
switch (particle.material) {
case Particle::MaterialType::WATER:
// Water flows more easily
particle.viscosity = 0.1f;
break;
case Particle::MaterialType::SAND:
// Sand has more friction
particle.friction = 0.8f;
break;
case Particle::MaterialType::ICE:
// Ice melts over time
if (particle.temperature > 0.0f) {
particle.temperature -= 0.1f;
}
break;
default:
break;
}
}
void ParticleManager::handleParticleCollision(Particle& p1, Particle& p2) {
// Simple collision response
if (p1.material == Particle::MaterialType::WATER &&
p2.material == Particle::MaterialType::SAND) {
// Water can displace sand
if (p1.density < p2.density) {
// Swap positions
std::swap(p1.position, p2.position);
}
}
}
void ParticleManager::updateParticleState(Particle& particle) {
// Update particle state based on conditions
if (particle.velocity.length() < 0.1f && particle.canFall) {
particle.state = Particle::State::SETTLED;
} else if (particle.velocity.length() > 0.5f && particle.canFlow) {
particle.state = Particle::State::FLOWING;
} else if (particle.temperature > 100.0f) {
particle.state = Particle::State::GAS;
}
}

View file

@ -0,0 +1,83 @@
#pragma once
#include "Particle.h"
#include "ParticleSystem.h"
#include <vector>
#include <memory>
#include <unordered_map>
#include <string>
// ParticleManager class header - high-level particle management and coordination
class ParticleManager {
public:
ParticleManager();
~ParticleManager() = default;
// Disable copying
ParticleManager(const ParticleManager&) = delete;
ParticleManager& operator=(const ParticleManager&) = delete;
// Core management methods
void initialize(int width, int height);
void update(float deltaTime);
void cleanup();
// Particle system management
std::shared_ptr<ParticleSystem> createParticleSystem(int width, int height, const std::string& name);
void destroyParticleSystem(const std::string& name);
std::shared_ptr<ParticleSystem> getParticleSystem(const std::string& name);
std::shared_ptr<ParticleSystem> getDefaultParticleSystem();
// Global particle operations
void addParticleToSystem(const std::string& systemName, const Particle& particle);
void addParticleToDefault(const Particle& particle);
void clearAllSystems();
void resetAllSystems();
// Material management across systems
void placeMaterialInSystem(const std::string& systemName, int x, int y, Particle::MaterialType material);
void placeMaterialInDefault(int x, int y, Particle::MaterialType material);
void floodFill(const std::string& systemName, int startX, int startY, Particle::MaterialType material);
// Simulation control
void pauseSimulation(const std::string& systemName);
void resumeSimulation(const std::string& systemName);
void setSimulationSpeed(float speed, const std::string& systemName);
bool isSimulationPaused(const std::string& systemName) const;
// Statistics and monitoring
int getTotalParticleCount() const;
int getSystemCount() const;
std::vector<std::string> getSystemNames() const;
void printSystemStats() const;
// File I/O
bool saveSystemToFile(const std::string& systemName, const std::string& filename);
bool loadSystemFromFile(const std::string& systemName, const std::string& filename);
bool exportSystemsAsImage(const std::string& systemName, const std::string& filename);
private:
// Core data structures
std::unordered_map<std::string, std::shared_ptr<ParticleSystem>> systems_;
std::shared_ptr<ParticleSystem> defaultSystem_;
// System state
bool initialized_;
bool globalPaused_;
float globalSimulationSpeed_;
// Default system settings
int defaultWidth_;
int defaultHeight_;
// Helper methods
void updateSystem(const std::string& systemName, float deltaTime);
bool validateSystemName(const std::string& name) const;
std::string generateUniqueSystemName(const std::string& baseName) const;
void logSystemOperation(const std::string& operation, const std::string& systemName = "");
// Material interaction helpers
void applyMaterialPhysics(Particle& particle, Particle::MaterialType material);
void handleParticleCollision(Particle& p1, Particle& p2);
void updateParticleState(Particle& particle);
};

View file

@ -0,0 +1,276 @@
#include "ParticleSystem.h"
#include "../utils/Utils.h"
#include <algorithm>
// ParticleSystem class implementation - define system methods here
ParticleSystem::ParticleSystem(int width, int height)
: width_(width)
, height_(height)
, particleCount_(0) {
initializeGrid();
}
void ParticleSystem::initializeGrid() {
grid_.resize(height_);
for (auto& row : grid_) {
row.resize(width_);
// Initialize each cell to nullptr
}
}
void ParticleSystem::update(float deltaTime) {
// Update all active particles
for (auto& particle : particles_) {
if (particle && particle->active) {
updateParticlePosition(*particle, deltaTime);
}
}
// Clean up inactive particles
particles_.erase(
std::remove_if(particles_.begin(), particles_.end(),
[](const std::unique_ptr<Particle>& p) { return !p->active; }),
particles_.end()
);
}
void ParticleSystem::step() {
// Single physics step - apply gravity and friction
for (auto& particle : particles_) {
if (particle && particle->active && particle->canFall) {
// Check if particle would cross boundary before attempting movement
if (!wouldCrossBoundary(*particle, {0, 1})) {
// Try to move particle down
if (!tryMoveParticle(*particle, {0, 1})) {
// If particle can't move down, try diagonal moves
if (particle->canFlow) {
if (!wouldCrossBoundary(*particle, {-1, 1}) && !tryMoveParticle(*particle, {-1, 1})) {
if (!wouldCrossBoundary(*particle, {1, 1})) {
tryMoveParticle(*particle, {1, 1});
}
}
}
}
}
}
}
}
void ParticleSystem::addParticle(const Particle& particle) {
int x = static_cast<int>(particle.position.x);
int y = static_cast<int>(particle.position.y);
// Ensure particle is within boundaries
if (x < 0 || x >= width_ || y < 0 || y >= height_) {
Utils::log("Cannot add particle outside boundaries at (" + std::to_string(x) + ", " + std::to_string(y) + ")");
return; // Particle can't be placed outside boundaries
}
if (!isEmpty(x, y)) {
return; // Position already occupied
}
auto newParticle = std::make_unique<Particle>(particle);
particles_.push_back(std::move(newParticle));
grid_[y][x] = std::move(newParticle);
particleCount_++;
}
void ParticleSystem::removeParticle(int x, int y) {
// Ensure position is within boundaries
if (x < 0 || x >= width_ || y < 0 || y >= height_) {
Utils::log("Cannot remove particle outside boundaries at (" + std::to_string(x) + ", " + std::to_string(y) + ")");
return;
}
if (grid_[y][x]) {
grid_[y][x]->active = false;
grid_[y][x].reset();
particleCount_--;
}
}
Particle* ParticleSystem::getParticle(int x, int y) {
// Ensure position is within boundaries
if (x < 0 || x >= width_ || y < 0 || y >= height_) {
return nullptr;
}
return grid_[y][x].get();
}
bool ParticleSystem::isEmpty(int x, int y) const {
// Ensure position is within boundaries
if (x < 0 || x >= width_ || y < 0 || y >= height_) {
return false;
}
return grid_[y][x] == nullptr;
}
void ParticleSystem::placeMaterial(int x, int y, Particle::MaterialType material) {
// Ensure position is within boundaries
if (x < 0 || x >= width_ || y < 0 || y >= height_) {
Utils::log("Cannot place material outside boundaries at (" + std::to_string(x) + ", " + std::to_string(y) + ")");
return;
}
if (!isEmpty(x, y)) {
return; // Position already occupied
}
Particle newParticle(material, {static_cast<float>(x), static_cast<float>(y)});
addParticle(newParticle);
}
bool ParticleSystem::isValidPosition(int x, int y) const {
// Check if position is within boundaries
return x >= 0 && x < width_ && y >= 0 && y < height_;
}
void ParticleSystem::updateParticlePosition(Particle& particle, float deltaTime) {
// Apply gravity
particle.acceleration.y += particle.gravity;
// Update velocity
particle.velocity += particle.acceleration * deltaTime;
// Limit terminal velocity
if (particle.velocity.y > 10.0f) {
particle.velocity.y = 10.0f;
}
// Update position
particle.position += particle.velocity * deltaTime;
// Apply boundary constraints
applyBoundaryConstraints(particle);
// Additional boundary collision detection
if (particle.position.x < 0 || particle.position.x >= width_ ||
particle.position.y < 0 || particle.position.y >= height_) {
// Particle somehow got outside boundaries, clamp it back
clampParticleToBoundaries(particle);
// Log boundary violation for debugging
Utils::log("Particle boundary violation corrected at (" + std::to_string(particle.position.x) + ", " + std::to_string(particle.position.y) + ")");
}
// Reset acceleration
particle.acceleration = {0.0f, 0.0f};
}
bool ParticleSystem::tryMoveParticle(Particle& particle, const glm::vec2& direction) {
int newX = static_cast<int>(particle.position.x + direction.x);
int newY = static_cast<int>(particle.position.y + direction.y);
// Check boundary constraints
if (newX < 0 || newX >= width_ || newY < 0 || newY >= height_) {
return false;
}
if (!isEmpty(newX, newY)) {
return false;
}
// Get current grid position
int currentX = static_cast<int>(particle.position.x);
int currentY = static_cast<int>(particle.position.y);
if (!isValidPosition(currentX, currentY)) {
return false;
}
// Move particle in grid
grid_[newY][newX] = std::move(grid_[currentY][currentX]);
grid_[currentY][currentX].reset();
// Update particle position
particle.position = {static_cast<float>(newX), static_cast<float>(newY)};
return true;
}
void ParticleSystem::applyBoundaryConstraints(Particle& particle) {
// Use the dedicated boundary collision handler
handleBoundaryCollision(particle);
}
bool ParticleSystem::isAtBoundary(const Particle& particle) const {
return particle.position.x <= 0 ||
particle.position.x >= width_ - 1 ||
particle.position.y <= 0 ||
particle.position.y >= height_ - 1;
}
bool ParticleSystem::wouldCrossBoundary(const Particle& particle, const glm::vec2& direction) const {
glm::vec2 newPosition = particle.position + direction;
return newPosition.x < 0 ||
newPosition.x >= width_ ||
newPosition.y < 0 ||
newPosition.y >= height_;
}
void ParticleSystem::handleBoundaryCollision(Particle& particle) {
// Handle collision with left wall
if (particle.position.x < 0) {
particle.position.x = 0;
particle.velocity.x = 0;
particle.acceleration.x = 0;
}
// Handle collision with right wall
if (particle.position.x >= width_) {
particle.position.x = static_cast<float>(width_ - 1);
particle.velocity.x = 0;
particle.acceleration.x = 0;
}
// Handle collision with floor
if (particle.position.y >= height_) {
particle.position.y = static_cast<float>(height_ - 1);
particle.velocity.y = 0;
particle.acceleration.y = 0;
particle.state = Particle::State::SETTLED;
}
// Handle collision with ceiling
if (particle.position.y < 0) {
particle.position.y = 0;
particle.velocity.y = 0;
particle.acceleration.y = 0;
}
}
bool ParticleSystem::isMovingTowardsBoundary(const Particle& particle) const {
// Check if particle is moving towards any boundary
if (particle.velocity.x > 0 && particle.position.x >= width_ - 1) return true; // Moving right towards right wall
if (particle.velocity.x < 0 && particle.position.x <= 0) return true; // Moving left towards left wall
if (particle.velocity.y > 0 && particle.position.y >= height_ - 1) return true; // Moving down towards floor
if (particle.velocity.y < 0 && particle.position.y <= 0) return true; // Moving up towards ceiling
return false;
}
void ParticleSystem::clampParticleToBoundaries(Particle& particle) {
// Clamp particle position to boundaries
particle.position.x = std::max(0.0f, std::min(particle.position.x, static_cast<float>(width_ - 1)));
particle.position.y = std::max(0.0f, std::min(particle.position.y, static_cast<float>(height_ - 1)));
// Stop velocity if particle is at boundary
if (particle.position.x <= 0 || particle.position.x >= width_ - 1) {
particle.velocity.x = 0;
particle.acceleration.x = 0;
}
if (particle.position.y <= 0 || particle.position.y >= height_ - 1) {
particle.velocity.y = 0;
particle.acceleration.y = 0;
// If particle is at floor, mark it as settled
if (particle.position.y >= height_ - 1) {
particle.state = Particle::State::SETTLED;
}
}
}

View file

@ -0,0 +1,55 @@
#pragma once
#include "Particle.h"
#include <vector>
#include <memory>
// ParticleSystem class header - manages collections of particles
class ParticleSystem {
public:
ParticleSystem(int width, int height);
~ParticleSystem() = default;
// Disable copying
ParticleSystem(const ParticleSystem&) = delete;
ParticleSystem& operator=(const ParticleSystem&) = delete;
// Core simulation methods
void update(float deltaTime);
void step();
// Particle management methods
void addParticle(const Particle& particle);
void removeParticle(int x, int y);
bool isEmpty(int x, int y) const;
// Material placement methods
void placeMaterial(int x, int y, Particle::MaterialType material);
bool isValidPosition(int x, int y) const;
// Getter methods
int getWidth() const { return width_; }
int getHeight() const { return height_; }
int getParticleCount() const { return particleCount_; }
Particle* getParticle(int x, int y);
const std::vector<std::unique_ptr<Particle>>& getAllParticles() const { return particles_; }
private:
int width_;
int height_;
std::vector<std::vector<std::unique_ptr<Particle>>> grid_;
std::vector<std::unique_ptr<Particle>> particles_;
int particleCount_;
// Helper methods
void initializeGrid();
void updateParticlePosition(Particle& particle, float deltaTime);
bool tryMoveParticle(Particle& particle, const glm::vec2& direction);
void applyBoundaryConstraints(Particle& particle);
bool isAtBoundary(const Particle& particle) const;
bool wouldCrossBoundary(const Particle& particle, const glm::vec2& direction) const;
void handleBoundaryCollision(Particle& particle);
bool isMovingTowardsBoundary(const Particle& particle) const;
void clampParticleToBoundaries(Particle& particle);
};

View file

@ -1,4 +1,6 @@
#include "Renderer.h"
#include "../particles/Particle.h"
#include "../utils/Utils.h"
#include <iostream>
Renderer::Renderer(SDL_Window* window)
@ -71,3 +73,122 @@ void Renderer::fillRect(const SDL_Rect& rect) {
SDL_RenderFillRect(renderer_, &rect);
}
}
void Renderer::renderParticles(const std::vector<std::unique_ptr<Particle>>& particles, int screenWidth, int screenHeight) {
if (!renderer_) return;
// Set default sand color
setDrawColor(194, 178, 128, 255);
int activeParticles = 0;
for (const auto& particle : particles) {
if (particle && particle->active) {
// Check if particle is at boundary and change color accordingly
if (particle->position.x <= 0 || particle->position.x >= screenWidth - 1 ||
particle->position.y <= 0 || particle->position.y >= screenHeight - 1) {
// Boundary particles get a different color (red)
setDrawColor(255, 0, 0, 255);
} else {
// Regular sand color
setDrawColor(194, 178, 128, 255);
}
// Draw each particle as a small filled rectangle
SDL_Rect rect = {
static_cast<int>(particle->position.x - particle->size / 2),
static_cast<int>(particle->position.y - particle->size / 2),
static_cast<int>(particle->size),
static_cast<int>(particle->size)
};
fillRect(rect);
activeParticles++;
}
}
// Performance optimization: reduce logging frequency to every 120 frames (~2 seconds at 60 FPS)
static int frameCount = 0;
if (++frameCount % 120 == 0) {
Utils::log("Rendering " + std::to_string(activeParticles) + " active particles");
}
}
void Renderer::renderParticlesWithMaterials(const std::vector<std::unique_ptr<Particle>>& particles, int screenWidth, int screenHeight) {
if (!renderer_) return;
int activeParticles = 0;
for (const auto& particle : particles) {
if (particle && particle->active) {
// Set color based on material type
switch (particle->material) {
case Particle::MaterialType::SAND:
setDrawColor(194, 178, 128, 255); // Sand color
break;
case Particle::MaterialType::WATER:
setDrawColor(0, 100, 255, 255); // Blue
break;
case Particle::MaterialType::ROCK:
setDrawColor(128, 128, 128, 255); // Gray
break;
case Particle::MaterialType::ICE:
setDrawColor(200, 220, 255, 255); // Light blue
break;
case Particle::MaterialType::METAL:
setDrawColor(192, 192, 192, 255); // Silver
break;
default:
setDrawColor(194, 178, 128, 255); // Default sand color
break;
}
// Check if particle is at boundary and override color
if (particle->position.x <= 0 || particle->position.x >= screenWidth - 1 ||
particle->position.y <= 0 || particle->position.y >= screenHeight - 1) {
// Boundary particles get a different color (red)
setDrawColor(255, 0, 0, 255);
}
// Draw each particle as a small filled rectangle
SDL_Rect rect = {
static_cast<int>(particle->position.x - particle->size / 2),
static_cast<int>(particle->position.y - particle->size / 2),
static_cast<int>(particle->size),
static_cast<int>(particle->size)
};
fillRect(rect);
activeParticles++;
}
}
// Performance optimization: reduce logging frequency to every 120 frames (~2 seconds at 60 FPS)
static int frameCount = 0;
if (++frameCount % 120 == 0) {
Utils::log("Rendering " + std::to_string(activeParticles) + " active particles with materials");
}
}
void Renderer::setParticleColor(Uint8 r, Uint8 g, Uint8 b, Uint8 a) {
setDrawColor(r, g, b, a);
}
void Renderer::drawBoundaries(int screenWidth, int screenHeight, bool showBoundaries) {
if (!renderer_ || !showBoundaries) return;
// Set boundary color (dark gray)
setDrawColor(64, 64, 64, 255);
// Draw floor (bottom boundary)
SDL_Rect floorRect = {0, screenHeight - 2, screenWidth, 2};
fillRect(floorRect);
// Draw left wall
SDL_Rect leftWallRect = {0, 0, 2, screenHeight};
fillRect(leftWallRect);
// Draw right wall
SDL_Rect rightWallRect = {screenWidth - 2, 0, 2, screenHeight};
fillRect(rightWallRect);
// Draw ceiling (optional - top boundary)
SDL_Rect ceilingRect = {0, 0, screenWidth, 2};
fillRect(ceilingRect);
}

View file

@ -2,6 +2,7 @@
#include "../core/Common.h"
#include <SDL2/SDL.h>
#include <vector>
class Renderer {
public:
@ -30,6 +31,14 @@ public:
void drawRect(const SDL_Rect& rect);
void fillRect(const SDL_Rect& rect);
// Particle rendering
void renderParticles(const std::vector<std::unique_ptr<class Particle>>& particles, int screenWidth, int screenHeight);
void renderParticlesWithMaterials(const std::vector<std::unique_ptr<class Particle>>& particles, int screenWidth, int screenHeight);
void setParticleColor(Uint8 r, Uint8 g, Uint8 b, Uint8 a);
// Boundary rendering
void drawBoundaries(int screenWidth, int screenHeight, bool showBoundaries = true);
private:
SDL_Renderer* renderer_;
};