diff --git a/.gitignore b/.gitignore index 27d5feb..64378cd 100644 --- a/.gitignore +++ b/.gitignore @@ -42,6 +42,7 @@ Debug/ Release/ x64/ x86/ +.cache/ # IDE and Editor files .vscode/ diff --git a/CMakeLists.txt b/CMakeLists.txt index c545de7..f53a88e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/src/core/Application.cpp b/src/core/Application.cpp index 87f5b7c..66fe57f 100644 --- a/src/core/Application.cpp +++ b/src/core/Application.cpp @@ -1,10 +1,22 @@ #include "Application.h" #include "Config.h" #include "../utils/Utils.h" +#include "../core/InputHandler.h" +#include "../particles/ParticleFactory.h" +#include +#include #include +#include +#include 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(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_->initialize(width, height); + // Create event manager eventManager_ = std::make_unique(); + // Create input handler + inputHandler_ = std::make_unique(); + + // Create particle factory + particleFactory_ = std::make_unique(); + + // 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(start.x), static_cast(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 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"); + } + } +} diff --git a/src/core/Application.h b/src/core/Application.h index a6a6762..d612c54 100644 --- a/src/core/Application.h +++ b/src/core/Application.h @@ -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_; + std::unique_ptr inputHandler_; + std::unique_ptr 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); }; diff --git a/src/core/Config.h b/src/core/Config.h index 286ae0f..b309c7f 100644 --- a/src/core/Config.h +++ b/src/core/Config.h @@ -1,7 +1,5 @@ #pragma once -#include - 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; diff --git a/src/core/InputHandler.cpp b/src/core/InputHandler.cpp new file mode 100644 index 0000000..f3a3a92 --- /dev/null +++ b/src/core/InputHandler.cpp @@ -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); + } +} diff --git a/src/core/InputHandler.h b/src/core/InputHandler.h new file mode 100644 index 0000000..a05004d --- /dev/null +++ b/src/core/InputHandler.h @@ -0,0 +1,63 @@ +#pragma once + +#include +#include +#include + +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 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; +}; diff --git a/src/events/EventManager.cpp b/src/events/EventManager.cpp index 3c5ddfc..2b9a195 100644 --- a/src/events/EventManager.cpp +++ b/src/events/EventManager.cpp @@ -1,7 +1,13 @@ #include "EventManager.h" -#include +#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"); +} diff --git a/src/events/EventManager.h b/src/events/EventManager.h index f1c9b76..0f0ee61 100644 --- a/src/events/EventManager.h +++ b/src/events/EventManager.h @@ -4,6 +4,8 @@ #include #include +class ParticleManager; + enum class EventType { Quit, KeyDown, @@ -33,6 +35,9 @@ public: using EventCallback = std::function; 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 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); }; diff --git a/src/particles/Particle.cpp b/src/particles/Particle.cpp new file mode 100644 index 0000000..74c767a --- /dev/null +++ b/src/particles/Particle.cpp @@ -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; + } +} diff --git a/src/particles/Particle.h b/src/particles/Particle.h new file mode 100644 index 0000000..f9c0ad1 --- /dev/null +++ b/src/particles/Particle.h @@ -0,0 +1,60 @@ +#pragma once + +#include +#include + +// 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); +}; diff --git a/src/particles/ParticleFactory.cpp b/src/particles/ParticleFactory.cpp new file mode 100644 index 0000000..e96d5d6 --- /dev/null +++ b/src/particles/ParticleFactory.cpp @@ -0,0 +1,80 @@ +#include "ParticleFactory.h" +#include +#include + +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 ParticleFactory::createParticlesAlongPath(const glm::vec2& start, const glm::vec2& end, float spacing) { + std::vector 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(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 ParticleFactory::createParticlesInArea(const glm::vec2& center, float radius, int count) { + std::vector 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(rand()) / RAND_MAX); +} diff --git a/src/particles/ParticleFactory.h b/src/particles/ParticleFactory.h new file mode 100644 index 0000000..8ad14c7 --- /dev/null +++ b/src/particles/ParticleFactory.h @@ -0,0 +1,41 @@ +#pragma once + +#include "Particle.h" +#include +#include +#include + +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 createParticlesAlongPath(const glm::vec2& start, const glm::vec2& end, float spacing = 8.0f); + std::vector 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); +}; diff --git a/src/particles/ParticleManager.cpp b/src/particles/ParticleManager.cpp new file mode 100644 index 0000000..648b6d9 --- /dev/null +++ b/src/particles/ParticleManager.cpp @@ -0,0 +1,366 @@ +#include "ParticleManager.h" +#include "core/Config.h" +#include "../utils/Utils.h" +#include +#include + +// 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 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(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 ParticleManager::getParticleSystem(const std::string& name) { + auto it = systems_.find(name); + return (it != systems_.end()) ? it->second : nullptr; +} + +std::shared_ptr 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 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(systems_.size()); +} + +std::vector ParticleManager::getSystemNames() const { + std::vector 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; + } +} diff --git a/src/particles/ParticleManager.h b/src/particles/ParticleManager.h new file mode 100644 index 0000000..370e101 --- /dev/null +++ b/src/particles/ParticleManager.h @@ -0,0 +1,83 @@ +#pragma once + +#include "Particle.h" +#include "ParticleSystem.h" +#include +#include +#include +#include + +// 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 createParticleSystem(int width, int height, const std::string& name); + void destroyParticleSystem(const std::string& name); + std::shared_ptr getParticleSystem(const std::string& name); + std::shared_ptr 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 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> systems_; + std::shared_ptr 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); +}; diff --git a/src/particles/ParticleSystem.cpp b/src/particles/ParticleSystem.cpp new file mode 100644 index 0000000..592da26 --- /dev/null +++ b/src/particles/ParticleSystem.cpp @@ -0,0 +1,276 @@ +#include "ParticleSystem.h" +#include "../utils/Utils.h" +#include + +// 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& 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(particle.position.x); + int y = static_cast(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); + 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(x), static_cast(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(particle.position.x + direction.x); + int newY = static_cast(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(particle.position.x); + int currentY = static_cast(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(newX), static_cast(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(width_ - 1); + particle.velocity.x = 0; + particle.acceleration.x = 0; + } + + // Handle collision with floor + if (particle.position.y >= height_) { + particle.position.y = static_cast(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(width_ - 1))); + particle.position.y = std::max(0.0f, std::min(particle.position.y, static_cast(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; + } + } +} diff --git a/src/particles/ParticleSystem.h b/src/particles/ParticleSystem.h new file mode 100644 index 0000000..71ece8e --- /dev/null +++ b/src/particles/ParticleSystem.h @@ -0,0 +1,55 @@ +#pragma once + +#include "Particle.h" +#include +#include + +// 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>& getAllParticles() const { return particles_; } + + private: + int width_; + int height_; + std::vector>> grid_; + std::vector> 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); +}; diff --git a/src/renderer/Renderer.cpp b/src/renderer/Renderer.cpp index ffbde71..98f0d17 100644 --- a/src/renderer/Renderer.cpp +++ b/src/renderer/Renderer.cpp @@ -1,4 +1,6 @@ #include "Renderer.h" +#include "../particles/Particle.h" +#include "../utils/Utils.h" #include 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>& 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(particle->position.x - particle->size / 2), + static_cast(particle->position.y - particle->size / 2), + static_cast(particle->size), + static_cast(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>& 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(particle->position.x - particle->size / 2), + static_cast(particle->position.y - particle->size / 2), + static_cast(particle->size), + static_cast(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); +} diff --git a/src/renderer/Renderer.h b/src/renderer/Renderer.h index acf4490..0debcbf 100644 --- a/src/renderer/Renderer.h +++ b/src/renderer/Renderer.h @@ -2,6 +2,7 @@ #include "../core/Common.h" #include +#include 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>& particles, int screenWidth, int screenHeight); + void renderParticlesWithMaterials(const std::vector>& 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_; };