diff --git a/App/CMakeLists.txt b/App/CMakeLists.txt index 97fee2d3e586a17c77fad8cc24d5f61a4b8c5305..e42fbe0db72cfc1f2543dc806bce673688f0c96f 100644 --- a/App/CMakeLists.txt +++ b/App/CMakeLists.txt @@ -1,9 +1,11 @@ add_executable( QPongGame GameLayer.cpp Log.cpp + Player.cpp MainMenuLayer.cpp QPongApp.cpp Window.cpp + Utils.cpp ) target_include_directories( QPong PUBLIC @@ -16,11 +18,9 @@ target_include_directories( QPong PUBLIC target_link_libraries( QPongGame PUBLIC GuiCore QPong - fftw3 - fftw3_omp ) add_custom_command(TARGET QPongGame POST_BUILD - COMMAND ln -sf ${PROJECT_SOURCE_DIR}/assets ${CMAKE_BINARY_DIR}/QPongApp + COMMAND ln -sf ${PROJECT_SOURCE_DIR}/assets ${CMAKE_BINARY_DIR}/App ) \ No newline at end of file diff --git a/App/GameLayer.cpp b/App/GameLayer.cpp index eccb93e116b4bee556b189aac48f25e36bc5c555..a01442d7fa0322000d5a420a05a7152c56b767db 100644 --- a/App/GameLayer.cpp +++ b/App/GameLayer.cpp @@ -17,304 +17,66 @@ #include "GameLayer.hpp" #include "Log.hpp" +#include "Utils.hpp" #include <string> #include <thread> - -void calculatePointPositions(float *points, int arraySize); -void calculateMomentumViewPositions(float *points, int arraySize); -void calculateTriangleIndices(uint32_t *indices, int arraySize); -void calculateBorderPotential(float *potential, double *yAt, int arrayElements); - - const std::string vertexShaderPath = "assets/shaders/particle.vert.glsl"; const std::string fragmentShaderPath = "assets/shaders/particle.frag.glsl"; -const QPong::ParticleProps initialParticleSettings = { +const QPong::Particle::Properties initialParticleSettings = { + .size = 512, .position = {0.0, 0.0, 0.0}, .momentum = {0.0, 0.0, 0.0}, - .pointUnsharpness = 100.0, + .pointUnsharpness = 64.0, .startValue = 0.01, .hbar = 0.000125 }; -template <typename T> -const T pow2(const T v) -{ - return v * v; -} - - -void QPong::calculatePointPositions(float *positions, int arraySize) -{ - constexpr float size{3.175f}; - constexpr float xOffset{0.0f}; - constexpr float yOffset{-0.175f}; - - constexpr float width{size}; - constexpr float xMin{-width / 2.0}; - constexpr float xMax{width / 2.0}; - constexpr float height{width}; - constexpr float yMin{-height / 2.0}; - constexpr float yMax{(height / 2.0)}; - - float dx = (xMax - xMin) / (arraySize - 1.0f); // "spacing" - float dy = (yMax - yMin) / (arraySize - 1.0f); - - uint32_t i {0}; // array index - for (int yId{0}; yId < arraySize; ++yId) - { - for (int xId{0}; xId < arraySize; ++xId) - { - positions[i++] = xMin + xOffset + (static_cast<float>(xId) * dx); // x-Pos - positions[i++] = yMax + yOffset - (static_cast<float>(yId) * dy); // y-Pos - positions[i++] = 0.0f; // z-Pos - } - } -} - -void QPong::calculateMomentumViewPositions(float *positions, int arraySize) -{ - constexpr float xOffset {1.5f}; // has to be the same as in the shader - float xMin {2.2f + xOffset}; - float xMax {0.2f + xOffset}; - float yMin {-1.0f}; - float yMax {1.0f}; - float dx = (xMax - xMin) / (arraySize - 1.0f); // "spacing" - float dy = (yMax - yMin) / (arraySize - 1.0f); - { - uint32_t i {0}; // array index - for (int yId{0}; yId < arraySize; ++yId) - { - for (int xId{0}; xId < arraySize; ++xId) - { - positions[i++] = xMin + (static_cast<float>(xId) * dx); // x-Pos - positions[i++] = yMax - (static_cast<float>(yId) * dy); // y-Pos - positions[i++] = 0.0f; // z-Pos - } - } - } -} - -void QPong::calculateTriangleIndices(uint32_t *indices, int arraySize) -{ - uint32_t i{0}; - for (int row {0}; row < (arraySize - 1); ++row) - { - for (int k {0}; k < (arraySize - 1); ++k) - { - int offset = row * arraySize; - indices[i++] = offset + k; - indices[i++] = offset + k + 1; - indices[i++] = offset + k + arraySize; - indices[i++] = offset + k + 1; - indices[i++] = offset + k + arraySize; - indices[i++] = offset + k + arraySize + 1; - } - } -} - -void QPong::calculateBorderPotential(float *potential, double *yAt, int arrayElements) -{ - double y {0.0}; - for (int i {0}; i < arrayElements; ++i) - { - y = yAt[i]; - float pot = pow2(y) * pow2(y) * pow2(y) * pow2(y) * pow2(y) * pow2(y) * pow2(y) * 0.02; - potential[i] = pot; - } -} - - +const QPong::Particle::GameOptions intialOptions = { + .potentialsEnabled = true, + .absorbtionEnabled = true, + .momentumEnabled = false +}; -QPong::GameLayer::GameLayer(int resolution) +QPong::GameLayer::GameLayer(int resolution, int threads) :Layer("GameLayer") , m_arraySize{resolution} , m_propagate{false} , m_leftBat(-0.8, 0.0, Key::W, Key::S) , m_rightBat(0.8, 0.0, Key::Up, Key::Down) - , m_particleProperties {initialParticleSettings} + , m_options{intialOptions} + , m_properties {initialParticleSettings} , m_speedScale{5.0f} , m_randomMomentum {true} - , m_useThreads {false} -{ - srand (time(NULL)); - m_arrayElements = m_arraySize * m_arraySize; - m_numberOfQuads = (m_arraySize - 1) * (m_arraySize - 1); - constexpr int indicesPerQuad {6}; // two triangles - m_numberOfIndices = m_numberOfQuads * indicesPerQuad; -} - -void QPong::GameLayer::initialiseParticleMomentum() -{ - constexpr std::complex<double> ci {0.0, 1.0}; // complex number - const double hbar = m_particleProperties.hbar; - const double A0 = m_particleProperties.startValue; - const double px0 = m_particleProperties.momentum.x / 20.0; - const double py0 = m_particleProperties.momentum.y / 20.0; - const double x0 = m_particleProperties.position.x; - const double y0 = m_particleProperties.position.y; - const double lambda = m_particleProperties.pointUnsharpness; - - double sum {0.0}; - m_particleAbsouluteSumStart = 0.0; - double y{0.0}; - double x{0.0}; - for (auto i {0}; i < m_arrayElements; ++i) - { - x = m_xAt[i]; - y = m_yAt[i]; - m_psi[i] = A0 * exp( ci / hbar * ( x * px0 + y * py0) - lambda * (pow2(x-x0) + pow2(y-y0))); - sum += pow2(std::real(m_psi[i])) + pow2(std::imag(m_psi[i])); - } - for (auto i {0}; i < m_arrayElements; ++i) - { - m_psi[i] *= (10.0 * (m_arraySize / 512.0)) / sqrt(sum); - m_particleAbsouluteSumStart += pow2(std::real(m_psi[i])) + pow2(std::imag(m_psi[i])); - } - Log::get()->trace("Particle initialised"); -} - -double QPong::GameLayer::absorbParticle(int i, double c) -{ - double normPrevious = pow2(std::real(m_psi[i])) + pow2(std::imag(m_psi[i])); - double absorb = pow2(std::cos(c * 10)); - m_psi[i] *= absorb; - double delta = normPrevious - (pow2(std::real(m_psi[i])) + pow2(std::imag(m_psi[i]))); - return delta; -} - -void QPong::GameLayer::applyPotentials(int indexBegin, int indexEnd, double dt) -{ - constexpr std::complex<double> ci {0.0, 1.0}; - const double hbar = m_particleProperties.hbar; - double x {0.0}; - double y {0.0}; - const double potential = 1.0; - for (int i {indexBegin}; i < indexEnd; ++i) - { - double potSum = 1.0; - if (m_potentialsEnabled) - { - potSum = 0.0; - x = m_xAt[i]; - y = m_yAt[i]; - if (m_removeParticleEnabled) - { - if (x < m_leftBat.getX()) - { - double distanceToEdge = -m_leftBat.getX() + x; - double c = distanceToEdge * (M_PI / 2.0) / (1.0 + m_leftBat.getX()); - m_playerTwo.currentlyScored += absorbParticle(i, c); - } - if (x>m_rightBat.getX()) - { - double distanceToEdge = x - m_rightBat.getX(); - double c = distanceToEdge * (M_PI / 2.0) / (1.0 + m_rightBat.getX()); - m_playerOne.currentlyScored += absorbParticle(i, c); - } - } - double potLeftBat = m_leftBat.getPotential(x, y); - double potRightBat = m_rightBat.getPotential(x, y); - potSum = potLeftBat + potRightBat + m_staticBorderPotential[i]; - } - auto psi = m_psi[i] * exp(-ci * dt/hbar * potential * potSum); - m_psi[i] = psi; - if (m_potentialsEnabled) - { - m_dynamicPotential[i] = potSum; - } - } -} - -void QPong::GameLayer::moveForward(int indexBegin, int indexEnd, double dt) -{ - constexpr std::complex<double> ci {0.0, 1.0}; - const double hbar = m_particleProperties.hbar; - const double N = 1.0 / static_cast<double>(m_arrayElements); - for (int i{indexBegin}; i<indexEnd; ++i) - { - m_psi[i] *= exp(-ci * dt / hbar * m_E_At[i]) * N; - } -} - -void QPong::GameLayer::updateMomentumView(int indexBegin, int indexEnd) { - GuiCore::Timer timer("updateMomentumView"); - int x {0}; - int y {0}; - for (int i {indexBegin}; i< indexEnd; ++i) - { - x = i % m_arraySize; - y = i / m_arraySize; - auto halfArraySize = m_arraySize / 2; - - if (y > halfArraySize) - { - y = y - halfArraySize; - } - else - { - y = y + halfArraySize; - } + m_properties.size = resolution; + m_properties.threads = threads; + m_memorySize = sizeof(fftw_complex) * m_properties.elements(); - if (x > halfArraySize) - { - x = x - halfArraySize; - } - else - { - x = x + halfArraySize; - } - m_momentum[i] = m_psi[(y * m_arraySize )- x] * 100.0; - } -} - -void QPong::GameLayer::propagateStep(double dt) -{ - applyPotentials(0, m_arrayElements, dt); - - fftw_execute(m_planForward); - - moveForward(0, m_arrayElements, dt); + Log::get()->debug("Creating GameLayer"); - if (m_renderMomentum) - { - updateMomentumView(0, m_arrayElements); - } + srand(time(NULL)); + m_numberOfQuads = (m_arraySize - 1) * (m_arraySize - 1); + constexpr int indicesPerQuad{6}; // two triangles + m_numberOfIndices = m_numberOfQuads * indicesPerQuad; - fftw_execute(m_planBackwards); + m_staticBorderPotential = new float[m_properties.elements()]; + m_dynamicPotential = new float[m_properties.elements()]; + m_particle = std::make_unique<Particle>(m_properties, m_options, m_playerOne, m_playerTwo, m_leftBat, m_rightBat, m_staticBorderPotential, m_dynamicPotential); } void QPong::GameLayer::onAttach() { GuiCore::Glyphs::setup(); - m_xAt = new double[m_arrayElements]; - m_yAt = new double[m_arrayElements]; - m_E_At = new double[m_arrayElements]; - - for (int i {0}; i < m_arrayElements; ++i) - { - m_xAt[i] = xAtIndex(i, m_arraySize); - m_yAt[i] = yAtIndex(i, m_arraySize); - m_E_At[i] = (pow2(pxAtIndex(i, m_arraySize, m_particleProperties.hbar)) + pow2(pyAtIndex(i, m_arraySize, m_particleProperties.hbar))) / (2.0 * 1.0); - } - - m_renderMomentum = false; - m_removeParticleEnabled = true; - m_potentialsEnabled = true; - m_playerOne.totalScore = 0; - m_playerTwo.totalScore = 0; - m_shader = GuiCore::Shader::fromGLSLTextFiles( vertexShaderPath, - fragmentShaderPath - ); + fragmentShaderPath); // static verticie positions - m_particleViewPositions = new float[m_arrayElements * 3]; + m_particleViewPositions = new float[m_properties.elements() * 3]; calculatePointPositions(m_particleViewPositions, m_arraySize); // Vertex array to store all buffers @@ -324,7 +86,7 @@ void QPong::GameLayer::onAttach() // Buffer of all point locations glGenBuffers(1, &m_pointsBuffer); glBindBuffer(GL_ARRAY_BUFFER, m_pointsBuffer); - glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 3 * (m_arrayElements), m_particleViewPositions, GL_DYNAMIC_DRAW); + glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 3 * m_properties.elements(), m_particleViewPositions, GL_DYNAMIC_DRAW); glEnableVertexAttribArray(0); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(float) * 3, 0); @@ -335,12 +97,7 @@ void QPong::GameLayer::onAttach() glGenBuffers(1, &m_indicesBuffer); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_indicesBuffer); glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(uint32_t) * m_numberOfIndices, indices, GL_STATIC_DRAW); - delete(indices); - - // Setup particle buffer - m_memorySize = sizeof(fftw_complex) * m_arrayElements; - m_psi = static_cast<std::complex<double>*>(fftw_malloc(m_memorySize)); - m_momentum = static_cast<std::complex<double>*>(fftw_malloc(m_memorySize)); + delete (indices); glGenBuffers(1, &m_colorBuffer); glBindBuffer(GL_ARRAY_BUFFER, m_colorBuffer); @@ -349,72 +106,60 @@ void QPong::GameLayer::onAttach() glVertexAttribPointer(1, 2, GL_DOUBLE, GL_FALSE, sizeof(fftw_complex), 0); // Setup momentum view - m_momentumViewPositions = new float[m_arrayElements * 3]; + m_momentumViewPositions = new float[m_properties.elements() * 3]; calculateMomentumViewPositions(m_momentumViewPositions, m_arraySize); - // Initialise Borders - m_staticBorderPotential = new float[m_arrayElements]; - calculateBorderPotential(m_staticBorderPotential, m_yAt, m_arrayElements); - - m_dynamicPotential = new float[m_arrayElements]; glGenBuffers(1, &m_potentialBuffer); glBindBuffer(GL_ARRAY_BUFFER, m_potentialBuffer); - glBufferData(GL_ARRAY_BUFFER, sizeof(float) * m_arrayElements, m_staticBorderPotential, GL_STATIC_DRAW); + glBufferData(GL_ARRAY_BUFFER, sizeof(float) * m_properties.elements(), m_staticBorderPotential, GL_STATIC_DRAW); glEnableVertexAttribArray(2); glVertexAttribPointer(2, 1, GL_FLOAT, GL_FALSE, sizeof(float), 0); // Set orthografic view projectin "camera" which will not change. - constexpr float viewWidth {3.2f}; - constexpr float viewHeight {1.8f}; + constexpr float viewWidth{3.2f}; + constexpr float viewHeight{1.8f}; m_viewProjectionPosition = glm::ortho(-viewWidth, viewWidth, -viewHeight, viewHeight, -1.0f, 1.0f); m_viewProjectionLocation = glGetUniformLocation(m_shader->getRendererID(), "u_viewProjection"); m_showPotentialsLocation = glGetUniformLocation(m_shader->getRendererID(), "u_showPotential"); m_isMomentumEnabled = glGetUniformLocation(m_shader->getRendererID(), "u_isMomentumEnabled"); - fftw_init_threads(); - m_planForward = fftw_plan_dft_2d(m_arraySize, m_arraySize, (fftw_complex *)m_psi, (fftw_complex *)m_psi, FFTW_FORWARD, FFTW_MEASURE); - m_planBackwards = fftw_plan_dft_2d(m_arraySize, m_arraySize, (fftw_complex *)m_psi, (fftw_complex *)m_psi, FFTW_BACKWARD, FFTW_MEASURE); - reset(); } void QPong::GameLayer::onDetach() -{ +{ glDeleteVertexArrays(1, &m_vertexArray); glDeleteBuffers(1, &m_pointsBuffer); glDeleteBuffers(1, &m_indicesBuffer); glDeleteBuffers(1, &m_colorBuffer); - fftw_free(m_psi); - delete(m_momentum); - delete(m_dynamicPotential); - delete(m_staticBorderPotential); - delete(m_xAt); - delete(m_yAt); - delete(m_E_At); - // delete(m_pyAt); + delete (m_dynamicPotential); + delete (m_staticBorderPotential); } -void QPong::GameLayer::onEvent(GuiCore::Event& event) +void QPong::GameLayer::onEvent(GuiCore::Event &event) { } void QPong::GameLayer::reset() { + m_playerOne.resetCurrentScore(); + m_playerTwo.resetCurrentScore(); + if (m_randomMomentum) { - int xRand = rand() % 100; - int yRand = rand() % 100; + int xRand = rand() % 64; + int yRand = rand() % 32; int positive = rand() % 2; - m_particleProperties.momentum.x = (1.0f - (2.0f * positive)) * (1.0 + (float)xRand / 120.0f); + m_properties.momentum.x = (1.0f - (2.0f * positive)) * (1.0 + (float)xRand / 120.0f); positive = rand() % 2; - m_particleProperties.momentum.y = (1.0f - (2.0f * positive)) * (1.0 + (float)yRand / 120.0f); + m_properties.momentum.y = (1.0f - (2.0f * positive)) * (1.0 + (float)yRand / 120.0f); } - initialiseParticleMomentum(); + m_particle.reset(new Particle(m_properties, m_options, m_playerOne, m_playerTwo, m_leftBat, m_rightBat, m_staticBorderPotential, m_dynamicPotential)); + m_restart = false; m_gameCounts = true; - m_playerOne.currentlyScored = 0.0; - m_playerTwo.currentlyScored = 0.0; - propagateStep(0.0); + m_particle->update(0.0); + Log::get()->debug("GameLayer reset done"); } void QPong::GameLayer::onUpdate(GuiCore::Timestep ts) @@ -427,145 +172,96 @@ void QPong::GameLayer::onUpdate(GuiCore::Timestep ts) { m_leftBat.onUpdate(ts); m_rightBat.onUpdate(ts); - propagateStep(ts.getSeconds() * m_speedScale); + m_particle->update(ts.getSeconds() * m_speedScale); if (m_gameCounts) { - if (m_playerOne.currentlyScored > 35.0) + if (m_playerOne.getCurrentGameScore() > 35.0) { - m_playerOne.totalScore++; + m_playerOne.gameWon(); m_gameCounts = false; m_propagate = false; } - if (m_playerTwo.currentlyScored > 35.0) + if (m_playerTwo.getCurrentGameScore() > 35.0) { - m_playerTwo.totalScore++; + m_playerTwo.gameWon(); m_gameCounts = false; m_propagate = false; } } } - - // Particle + + GuiCore::Timer openGlTimer{"", false}; + // Particle + glBindBuffer(GL_ARRAY_BUFFER, m_pointsBuffer); + glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(float) * m_properties.elements() * 3, m_particleViewPositions); + + glBindBuffer(GL_ARRAY_BUFFER, m_colorBuffer); + glBufferSubData(GL_ARRAY_BUFFER, 0, m_memorySize, m_particle->getPsi()); + + glBindBuffer(GL_ARRAY_BUFFER, m_potentialBuffer); + glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(float) * m_properties.elements(), m_dynamicPotential); + + glUseProgram(m_shader->getRendererID()); + glUniform1i(m_showPotentialsLocation, static_cast<int>(m_options.potentialsEnabled)); + glUniform1i(m_isMomentumEnabled, static_cast<int>(m_options.momentumEnabled)); + glUniformMatrix4fv(m_viewProjectionLocation, 1, GL_FALSE, &m_viewProjectionPosition[0][0]); + glBindVertexArray(m_vertexArray); + glDrawElements(GL_TRIANGLES, m_numberOfIndices, GL_UNSIGNED_INT, nullptr); + + // Momentum + if (m_options.momentumEnabled) + { glBindBuffer(GL_ARRAY_BUFFER, m_pointsBuffer); - glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(float) * m_arrayElements * 3, m_particleViewPositions); - + glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(float) * m_properties.elements() * 3, m_momentumViewPositions); + glBindBuffer(GL_ARRAY_BUFFER, m_colorBuffer); - glBufferSubData(GL_ARRAY_BUFFER, 0, m_memorySize, m_psi); - - glBindBuffer(GL_ARRAY_BUFFER, m_potentialBuffer); - glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(float) * m_arrayElements, m_dynamicPotential); + glBufferSubData(GL_ARRAY_BUFFER, 0, m_memorySize, m_particle->getMomentum()); glUseProgram(m_shader->getRendererID()); - glUniform1i(m_showPotentialsLocation, static_cast<int>(m_potentialsEnabled)); - glUniform1i(m_isMomentumEnabled, static_cast<int>(m_renderMomentum)); - glUniformMatrix4fv(m_viewProjectionLocation, 1, GL_FALSE, &m_viewProjectionPosition[0][0]); + glUniform1i(m_showPotentialsLocation, 0); glBindVertexArray(m_vertexArray); - glDrawElements(GL_TRIANGLES, m_numberOfIndices, GL_UNSIGNED_INT, nullptr); - // Momentum - if (m_renderMomentum) - { - glBindBuffer(GL_ARRAY_BUFFER, m_pointsBuffer); - glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(float) * m_arrayElements * 3, m_momentumViewPositions); - - glBindBuffer(GL_ARRAY_BUFFER, m_colorBuffer); - glBufferSubData(GL_ARRAY_BUFFER, 0, m_memorySize, m_momentum); - - glUseProgram(m_shader->getRendererID()); - glUniform1i(m_showPotentialsLocation, 0); - glBindVertexArray(m_vertexArray); - - glDrawElements(GL_TRIANGLES, m_numberOfIndices, GL_UNSIGNED_INT, nullptr); - } + glDrawElements(GL_TRIANGLES, m_numberOfIndices, GL_UNSIGNED_INT, nullptr); + } + m_openGlTimer = openGlTimer.stop(); std::stringstream totalScore; - totalScore << m_playerOne.totalScore << " : " << m_playerTwo.totalScore; + totalScore << m_playerOne.getTotalScore() << " : " << m_playerTwo.getTotalScore(); GuiCore::Glyphs::renderText(totalScore.str(), -0.32f, 1.45f, 0.0075f, {1.0, 1.0, 1.0}); } -void QPong::GameLayer::guiSelectThreadCount() -{ - /// @todo Rework threading more efficient. - // ImGui::Checkbox("Enable multiple threads", &m_useThreads); - if (m_useThreads) - { - if (ImGui::Button("Less threads")) - { - if (m_renderingThreadsNumber > 1) - { - bool isPowerOf2 = false; - while (!isPowerOf2) - { - m_renderingThreadsNumber--; - for (int i = 0; i < 10; i++) - { - if (m_renderingThreadsNumber == pow(2, i)) - { - isPowerOf2 = true; - break; - } - } - } - } - } - ImGui::SameLine(); - if (ImGui::Button("More Threads")) - { - if (m_renderingThreadsNumber < 200) - { - bool isPowerOf2 = false; - while (!isPowerOf2) - { - m_renderingThreadsNumber++; - for (int i = 0; i < 10; i++) - { - if (m_renderingThreadsNumber == pow(2, i)) - { - isPowerOf2 = true; - break; - } - } - } - } - } - ImGui::SameLine(); - ImGui::Text("%d", m_renderingThreadsNumber); - } -} - void QPong::GameLayer::onGuiRender() { // GUI settings for the particle ImGui::Begin("Particle Settings"); - + // Select initial momentum for particle at restart - ImGui::DragFloat2("Momentum [x,y]", glm::value_ptr(m_particleProperties.momentum), 0.01f, -3.0f, 3.0f); + ImGui::DragFloat2("Momentum [x,y]", glm::value_ptr(m_properties.momentum), 0.01f, -3.0f, 3.0f); ImGui::Checkbox("Random Momentum", &m_randomMomentum); - float pointUnsharpness = m_particleProperties.pointUnsharpness; + float pointUnsharpness = m_properties.pointUnsharpness; if (ImGui::DragFloat("Sharpness", &pointUnsharpness, 1.0f, 5.0f, 1000.0f)) { - m_particleProperties.pointUnsharpness = pointUnsharpness; + m_properties.pointUnsharpness = pointUnsharpness; } // Select hbar, changes apply on the fly. - float hbar = m_particleProperties.hbar; + float hbar = m_properties.hbar; if (ImGui::DragFloat("hbar", &hbar, 0.000001f, 0.000001f, 1.0f, "%.6f")) { - m_particleProperties.hbar = hbar; + m_properties.hbar = hbar; } // This value increases or decreases the time step made every iteration. ImGui::DragFloat("Speed Factor", &m_speedScale, 0.1, 0.1, 20.0); - if (ImGui::IsItemHovered()) { + if (ImGui::IsItemHovered()) + { ImGui::SetTooltip("Change the size of the timestep (dt)\nfor every iteration of the simulation."); } - guiSelectThreadCount(); + ImGui::Checkbox("Render Momentum", &m_options.momentumEnabled); + ImGui::Checkbox("Enable Potentials", &m_options.potentialsEnabled); + ImGui::Checkbox("Enable Particle absorption", &m_options.absorbtionEnabled); - ImGui::Checkbox("Render Momentum", &m_renderMomentum); - ImGui::Checkbox("Enable Potentials", &m_potentialsEnabled); - ImGui::Checkbox("Enable Particle absorption", &m_removeParticleEnabled); - // Start/Stop propagating if (m_propagate) { @@ -586,10 +282,17 @@ void QPong::GameLayer::onGuiRender() { m_restart = true; } - ImGui::Text("Player 1: %d", m_playerOne.totalScore); ImGui::SameLine(); - ImGui::Text(" | Live Score: %.1f\%%", m_playerOne.currentlyScored); + ImGui::Text("Player 1: %d", m_playerOne.getTotalScore()); + ImGui::SameLine(); + ImGui::Text(" | Live Score: %.1f\%%", m_playerOne.getCurrentGameScore()); + + ImGui::Text("Player 2: %d", m_playerTwo.getTotalScore()); + ImGui::SameLine(); + ImGui::Text(" | Live Score: %.1f\%%", m_playerTwo.getCurrentGameScore()); - ImGui::Text("Player 2: %d", m_playerTwo.totalScore); ImGui::SameLine(); - ImGui::Text(" | Live Score: %.1f\%%", m_playerTwo.currentlyScored); + ImGui::Spacing(); + ImGui::Text("FFT took %0.1lfms", m_particle->getTimeFFT()); + ImGui::Text("OpenGL took %.1lfms", m_openGlTimer); + ImGui::End(); } \ No newline at end of file diff --git a/App/GameLayer.hpp b/App/GameLayer.hpp index 6ad87e88549f71f02f78a02b99e4cba688b128a3..d92521f35a11a240f3ba0c4e722cdd2e00063fab 100644 --- a/App/GameLayer.hpp +++ b/App/GameLayer.hpp @@ -6,6 +6,7 @@ #define QPONG_GAME_LAYER_HPP #include <glm/glm.hpp> +#include <memory> #include <GuiCore/Layer.hpp> #include <GuiCore/Shader.hpp> @@ -14,30 +15,12 @@ #include <QPong/Bat.hpp> #include <QPong/Particle.hpp> -#include <complex> -#include <fftw3.h> - namespace QPong { - struct ParticleProps - { - glm::vec3 position; - glm::vec3 momentum; - double pointUnsharpness; - double startValue; - double hbar; - }; - - struct Player - { - double currentlyScored; - int totalScore; - }; - class GameLayer : public GuiCore::Layer { public: - GameLayer(int resolution); + GameLayer(int resolution, int threads); virtual ~GameLayer(){}; virtual void onAttach() override; virtual void onDetach() override; @@ -46,16 +29,13 @@ namespace QPong virtual void onEvent(GuiCore::Event &event) override; private: - void initialiseParticleMomentum(); - void propagateStep(double dt); - void applyPotentials(int indexBegin, int indexEnd, double dt); - void moveForward(int indexBegin, int indexEnd, double dt); - void updateMomentumView(int indexBegin, int indexEnd); - double absorbParticle(int i, double c); - void reset(); void guiSelectThreadCount(); + Particle::Properties m_properties; + Particle::GameOptions m_options; + std::unique_ptr<Particle> m_particle; + Bat m_leftBat; Bat m_rightBat; @@ -63,7 +43,6 @@ namespace QPong Player m_playerTwo; int m_arraySize; - int m_arrayElements; int m_numberOfQuads; int m_numberOfIndices; @@ -71,8 +50,8 @@ namespace QPong GuiCore::Shader *m_shader; GLuint m_vertexArray; ///< OpenGL object that "knows" all buffers. GLuint m_pointsBuffer; ///< Buffer with the location for each element in the particle array - GLuint m_indicesBuffer; ///< Indicies to draw trinagles from the points to fill the "canvas" - GLuint m_colorBuffer; ///< Reference for OpenGL to load the paritcle data to the GPU + GLuint m_indicesBuffer; ///< Indicies to draw triangles from the points to fill the "canvas" + GLuint m_colorBuffer; ///< Reference for OpenGL to load the particle data to the GPU GLuint m_potentialBuffer; ///< Borders and bats float *m_particleViewPositions; @@ -85,26 +64,13 @@ namespace QPong GLuint m_showPotentialsLocation; glm::mat4 m_viewProjectionPosition; - ParticleProps m_particleProperties; - fftw_plan m_planForward; - fftw_plan m_planBackwards; - double m_particleAbsouluteSumStart; - double *m_xAt; - double *m_yAt; - double *m_E_At; - std::complex<double> *m_psi; - std::complex<double> *m_momentum; - bool m_gameCounts; - bool m_useThreads; bool m_restart; bool m_propagate; - bool m_renderMomentum; bool m_randomMomentum; - bool m_removeParticleEnabled; - bool m_potentialsEnabled; float m_speedScale{1.0f}; - int m_renderingThreadsNumber{8}; + + double m_openGlTimer; }; } #endif \ No newline at end of file diff --git a/App/Log.cpp b/App/Log.cpp index f410f33ea88a41311f9e1f8925c3068c1958e071..da6b506047858746e2fc9c3f3a69e50c844e3d15 100644 --- a/App/Log.cpp +++ b/App/Log.cpp @@ -12,9 +12,22 @@ std::shared_ptr<spdlog::logger> Log::s_logger; void Log::setup() { - spdlog::set_pattern("%^[%T] %n: %v%$"); - s_logger = spdlog::stdout_color_mt("QPong"); - s_logger->set_level(spdlog::level::info); + if (!s_logger) + { + spdlog::set_pattern("%^[%T] %n: %v%$"); + s_logger = spdlog::stdout_color_mt("QPong"); +#ifdef ENABLE_LOG_DEBUG_INFO + s_logger->set_level(spdlog::level::trace); +#else + s_logger->set_level(spdlog::level::info); +#endif + s_logger->debug("Logger is setup"); + } + else + { + s_logger->debug("Logger already created"); + } + } std::shared_ptr<spdlog::logger> Log::get() diff --git a/App/MainMenuLayer.cpp b/App/MainMenuLayer.cpp index 75bd232c009e55911b465768543f3b3f2bd7c55d..65b4381455b5e77555997e154affcebf2614b4c7 100644 --- a/App/MainMenuLayer.cpp +++ b/App/MainMenuLayer.cpp @@ -6,6 +6,7 @@ #include <imgui/imgui.h> #include <GuiCore/Application.hpp> +#include "Log.hpp" #include "GameLayer.hpp" #include <glm/gtc/type_ptr.hpp> @@ -16,6 +17,8 @@ MainMenuLayer::MainMenuLayer() : m_selectedResolution {512} , m_powOfResolution {9} , m_forceIndividualResolution {false} + , m_checkboxThreads {true} + , m_workerThreads {8} , m_resetOnce {false} , m_gameAttached {false} { @@ -27,7 +30,7 @@ void MainMenuLayer::onAttach() glDepthFunc(GL_LESS); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - m_backgroundColor = {0.2f, 0.2f, 0.2f}; + m_backgroundColor = {0.15f, 0.15f, 0.15f}; createNewGame(); } @@ -38,13 +41,13 @@ void MainMenuLayer::onDetach() void MainMenuLayer::createNewGame() { - m_resetOnce = false; + m_resetOnce = false; if (m_gameAttached) { GuiCore::Application::get().popLayer(m_gameLayer); m_gameAttached = false; } - m_gameLayer = std::make_shared<GameLayer>(m_selectedResolution); + m_gameLayer = std::make_shared<GameLayer>(m_selectedResolution, m_workerThreads); GuiCore::Application::get().pushLayer(m_gameLayer); m_gameAttached = true; } @@ -115,6 +118,30 @@ void MainMenuLayer::guiResetGame() } } +void MainMenuLayer::guiNumberOfThreads() +{ + std::stringstream checkbox; + checkbox << "Use multiple threads [" << m_workerThreads << "]"; + ImGui::Checkbox(checkbox.str().c_str(), &m_checkboxThreads); + if (m_checkboxThreads) + { + if (ImGui::Button("Fewer Threads")) + { + m_workerThreads--; + } + ImGui::SameLine(); + if (ImGui::Button("More Threads")) + { + m_workerThreads++; + } + m_workerThreads = (m_workerThreads < 1) ? 1 : m_workerThreads; + m_workerThreads = (m_workerThreads > 32) ? 32 : m_workerThreads; + } + else + { + m_workerThreads = 1; + } +} void MainMenuLayer::onGuiRender() { @@ -124,6 +151,7 @@ void MainMenuLayer::onGuiRender() guiShowFPS(); guiSelectResolution(); guiResetGame(); + guiNumberOfThreads(); ImGui::ColorEdit3("Background Color", glm::value_ptr(m_backgroundColor)); diff --git a/App/MainMenuLayer.hpp b/App/MainMenuLayer.hpp index 4105159e42d4c6abc8c553d854b35d61df78e97a..4657b9afe41b9740289074969d6a7edfbe6625d1 100644 --- a/App/MainMenuLayer.hpp +++ b/App/MainMenuLayer.hpp @@ -32,12 +32,15 @@ private: void guiShowFPS(); void guiSelectResolution(); void guiResetGame(); + void guiNumberOfThreads(); // Resolution int m_powOfResolution; ///< choose only from resolutions with 2^pow int m_selectedResolution; ///< selected res in the gui bool m_forceIndividualResolution; ///< Override resoultion - + bool m_checkboxThreads; + int m_workerThreads; + bool m_resetOnce; ///< restart simulation if set std::shared_ptr<GuiCore::Layer> m_gameLayer; ///< holds ref to the game bool m_gameAttached; diff --git a/App/Player.cpp b/App/Player.cpp new file mode 100644 index 0000000000000000000000000000000000000000..83b317bfa189d1338510812a3ccbc5d94a7650a4 --- /dev/null +++ b/App/Player.cpp @@ -0,0 +1,35 @@ +/// @file Player.cpp +/// @author Armin Co +/// + +#include "Player.hpp" + +using namespace QPong; + +void Player::addScore(double score) +{ + m_lockCurrentScore.lock(); + m_currentlyScored += score; + m_lockCurrentScore.unlock(); +} + +void Player::gameWon() +{ + m_totalScore++; +} + +double Player::getCurrentGameScore() const +{ + return m_currentlyScored; +} + +int Player::getTotalScore() const +{ + return m_totalScore; +} + + +void Player::resetCurrentScore() +{ + m_currentlyScored = 0.0; +} \ No newline at end of file diff --git a/App/Player.hpp b/App/Player.hpp new file mode 100644 index 0000000000000000000000000000000000000000..89d2cf156f19c0eefe38fde2734de57d71ce2ad3 --- /dev/null +++ b/App/Player.hpp @@ -0,0 +1,44 @@ +/// @file Player.hpp +/// @author Armin Co +/// + +#ifndef QPONG_PLAYER_HPP +#define QPONG_PLAYER_HPP + +#include <mutex> + +namespace QPong +{ + class Player + { + public: + /// @brief Add up the score of the current step. + /// @param score Amount of particle absorpt in one step. + /// + void addScore(double score); + + /// @brief The player has won the game. + /// Increases the total score of the player by one. + /// + void gameWon(); + + /// @brief Get the score of the current game. + /// + double getCurrentGameScore() const; + + /// @brief Number of games the player has won. + /// + int getTotalScore() const; + + /// @brief Reset the temporary score for a new game. + /// + void resetCurrentScore(); + + private: + double m_currentlyScored = 0.0; + int m_totalScore = 0; + + std::mutex m_lockCurrentScore; + }; +} +#endif \ No newline at end of file diff --git a/App/Utils.cpp b/App/Utils.cpp new file mode 100644 index 0000000000000000000000000000000000000000..139ad7ae5e49f4e1e4a1bd7293fb41ff28d02fee --- /dev/null +++ b/App/Utils.cpp @@ -0,0 +1,85 @@ +/// @file Utils.cpp +/// @author Armin Co +/// + +#include "Utils.hpp" + +void calculatePointPositions(float *positions, int arraySize) +{ + constexpr float size{3.175f}; + constexpr float xOffset{0.0f}; + constexpr float yOffset{-0.175f}; + + constexpr float width{size}; + constexpr float xMin{-width / 2.0}; + constexpr float xMax{width / 2.0}; + constexpr float height{width}; + constexpr float yMin{-height / 2.0}; + constexpr float yMax{(height / 2.0)}; + + float dx = (xMax - xMin) / (arraySize - 1.0f); // "spacing" + float dy = (yMax - yMin) / (arraySize - 1.0f); + + uint32_t i {0}; // array index + for (int yId{0}; yId < arraySize; ++yId) + { + for (int xId{0}; xId < arraySize; ++xId) + { + positions[i++] = xMin + xOffset + (static_cast<float>(xId) * dx); // x-Pos + positions[i++] = yMax + yOffset - (static_cast<float>(yId) * dy); // y-Pos + positions[i++] = 0.0f; // z-Pos + } + } +} + +void calculateMomentumViewPositions(float *positions, int arraySize) +{ + constexpr float xOffset {1.5f}; // has to be the same as in the shader + float xMin {2.2f + xOffset}; + float xMax {0.2f + xOffset}; + float yMin {-1.0f}; + float yMax {1.0f}; + float dx = (xMax - xMin) / (arraySize - 1.0f); // "spacing" + float dy = (yMax - yMin) / (arraySize - 1.0f); + { + uint32_t i {0}; // array index + for (int yId{0}; yId < arraySize; ++yId) + { + for (int xId{0}; xId < arraySize; ++xId) + { + positions[i++] = xMin + (static_cast<float>(xId) * dx); // x-Pos + positions[i++] = yMax - (static_cast<float>(yId) * dy); // y-Pos + positions[i++] = 0.0f; // z-Pos + } + } + } +} + +void calculateTriangleIndices(uint32_t *indices, int arraySize) +{ + uint32_t i{0}; + for (int row {0}; row < (arraySize - 1); ++row) + { + for (int k {0}; k < (arraySize - 1); ++k) + { + int offset = row * arraySize; + indices[i++] = offset + k; + indices[i++] = offset + k + 1; + indices[i++] = offset + k + arraySize; + indices[i++] = offset + k + 1; + indices[i++] = offset + k + arraySize; + indices[i++] = offset + k + arraySize + 1; + } + } +} + +void calculateBorderPotential(float *potential, double *yAt, int arrayElements) +{ + double y {0.0}; + for (int i {0}; i < arrayElements; ++i) + { + y = yAt[i]; + float pot = pow2(y) * pow2(y) * pow2(y) * pow2(y) * pow2(y) * pow2(y) * pow2(y) * 0.02; + potential[i] = pot; + } +} \ No newline at end of file diff --git a/App/Utils.hpp b/App/Utils.hpp new file mode 100644 index 0000000000000000000000000000000000000000..93767da92f9c60adc80fc07cca81fe667128db1d --- /dev/null +++ b/App/Utils.hpp @@ -0,0 +1,21 @@ +/// @file Utils.hpp +/// @author Armin Co +/// + +#ifndef APP_UTILS_HPP +#define APP_UTILS_HPP + +#include <stdint.h> + +template <typename T> +const T pow2(const T v) +{ + return v * v; +} + +void calculatePointPositions(float *points, int arraySize); +void calculateMomentumViewPositions(float *points, int arraySize); +void calculateTriangleIndices(uint32_t *indices, int arraySize); +void calculateBorderPotential(float *potential, double *yAt, int arrayElements); + +#endif \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 27a4f1c98e7595d8156c6d1586ae15533bd81858..80f22469d91e52ede093515e988ceec899e21b95 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,7 +3,7 @@ project(QPong VERSION 0.0.1 LANGUAGES C CXX) set(CMAKE_CXX_STANDARD 20) set(CMAKE_BUILD_TYPE Release) -set(CMAKE_CXX_FLAGS "-O3") +set(CMAKE_CXX_FLAGS "-Ofast -flto") option(LOG_DEBUG_INFO "Enable logging t console" OFF) if(LOG_DEBUG_INFO) diff --git a/GuiCore/Application.cpp b/GuiCore/Application.cpp index 771a7195e36bbdaf94b85eacd6dcffa1e2ab8e6f..816300ee00c6d9fc10610ca4b497ca3211fe2872 100644 --- a/GuiCore/Application.cpp +++ b/GuiCore/Application.cpp @@ -17,12 +17,12 @@ namespace GuiCore if (s_instance == nullptr) { Log::setup(); - s_instance = this; - m_window = std::unique_ptr<IWindow>(IWindow::create({name, width, height})); - m_window->setEventCallback([this](auto &&... args) -> decltype(auto) { - return this->onEvent(std::forward<decltype(args)>(args)...); }); - m_guiLayer = std::make_shared<ImGuiLayer>(); - pushLayer(m_guiLayer); + s_instance = this; + m_window = std::unique_ptr<IWindow>(IWindow::create({name, width, height})); + m_window->setEventCallback([this](auto &&... args) -> decltype(auto) { + return this->onEvent(std::forward<decltype(args)>(args)...); }); + m_guiLayer = std::make_shared<ImGuiLayer>(); + pushLayer(m_guiLayer); } else { diff --git a/GuiCore/Timer.cpp b/GuiCore/Timer.cpp index 908adc57a163ec71b97320026d051461527fa5cf..e7f5ab11eeb70974bcc07392bcd7bcc56f3f89a2 100644 --- a/GuiCore/Timer.cpp +++ b/GuiCore/Timer.cpp @@ -7,8 +7,9 @@ using namespace GuiCore; -Timer::Timer(const char* name) +Timer::Timer(const char* name, bool log) : m_name{name} + , m_log {log} { start(); } @@ -33,7 +34,10 @@ double Timer::stop() auto duration = end - start; double ms = duration * 0.001; - Log::get()->debug("{0}: {1}ms", m_name, ms); + if (m_log) + { + Log::get()->trace("{0}: {1}ms", m_name, ms); + } return ms; } \ No newline at end of file diff --git a/GuiCore/Timer.hpp b/GuiCore/Timer.hpp index 999b537efe40e8adb8de8c80fcbb5f7cdb2232a0..bd78b3e37b17d39e4b2eb5545f97adb31cf918ee 100644 --- a/GuiCore/Timer.hpp +++ b/GuiCore/Timer.hpp @@ -15,7 +15,7 @@ class Timer public: /// @brief Start timer /// - Timer(const char* name = "Timer"); + Timer(const char* name = "Timer", bool log = true); /// @brief Stops timer. ~Timer(); @@ -25,6 +25,7 @@ public: private: std::chrono::time_point< std::chrono::high_resolution_clock > m_startTimepoint; const char* m_name; + bool m_log; }; } diff --git a/QPong/Bat.hpp b/QPong/Bat.hpp index 3b46a997e27fc5194249d2753d28eeb19a67a45a..9ab01a2be9d0a4bbdb4b9e02b44388b5118f6254 100644 --- a/QPong/Bat.hpp +++ b/QPong/Bat.hpp @@ -26,6 +26,7 @@ namespace QPong double m_width; int m_keyUp; int m_keyDown; + double m_absorbtParticle; }; } diff --git a/QPong/CMakeLists.txt b/QPong/CMakeLists.txt index 10028a94d1091711feb3706c9763846a0d745a9d..2dcd81edf0d6b1b99203f2d30345ed73644f7419 100644 --- a/QPong/CMakeLists.txt +++ b/QPong/CMakeLists.txt @@ -1,12 +1,15 @@ add_library(QPong Bat.cpp Particle.cpp + Workers.cpp ) target_include_directories(QPong PUBLIC .. ) -target_link_libraries( QPong +target_link_libraries( QPong PRIVATE GuiCore + fftw3 + fftw3_omp ) \ No newline at end of file diff --git a/QPong/Matchfield.cpp b/QPong/Matchfield.cpp deleted file mode 100644 index 402362fdf9c73b4d6e4b9d09b85231c83b26cfd3..0000000000000000000000000000000000000000 --- a/QPong/Matchfield.cpp +++ /dev/null @@ -1,16 +0,0 @@ -/// @file Matchfield.cpp -/// @author Armin Co -/// - -#include "Matchfield.hpp" - -#include <GuiCore/Input.hpp> - -using namespace QPong; - -Matchfield::Matchfield(unsigned size) - : m_leftBat(-0.8, 0.0, Key::W, Key::S) - , m_rightBat(0.8, 0.0, Key::Up, Key::Down) -{ - -} \ No newline at end of file diff --git a/QPong/Matchfield.hpp b/QPong/Matchfield.hpp deleted file mode 100644 index f6327ace07627c7664fa09cb733fcdb789a8d6b0..0000000000000000000000000000000000000000 --- a/QPong/Matchfield.hpp +++ /dev/null @@ -1,48 +0,0 @@ -/// @file Matchfield.hpp -/// @author Armin Co -/// - -#ifndef QPONG_MATCHFIELD_HPP -#define QPONG_MATCHFIELD_HPP - -#include <glm/glm.hpp> -#include "Bat.hpp" - -namespace QPong -{ - /// @brief Properties of the particle - /// - struct ParticleProperties - { - glm::vec3 position; - glm::vec3 momentum; - double pointUnsharpness; - double startValue; - double hbar; - }; - - /// @brief Score of a player. - /// - struct Player - { - double currentlyScored; - int totalScore; - }; - - class Matchfield - { - public: - Matchfield(unsigned size); - - private: - int m_size; - - Bat m_leftBat; - Bat m_rightBat; - - Player m_playerOne; - Player m_playerTwo; - }; -} - -#endif \ No newline at end of file diff --git a/QPong/Particle.cpp b/QPong/Particle.cpp index cd98359a5d713080e14dc323841bab159cf2aaae..ccf96e18ef9f022114ec2439bef7954ee1ad72ce 100644 --- a/QPong/Particle.cpp +++ b/QPong/Particle.cpp @@ -1,4 +1,17 @@ +/// @file Particle.cpp +/// @author Armin Co +/// + +#include <complex> + #include "Particle.hpp" +#include "Utils.hpp" +#include "Log.hpp" +#include "Worker.hpp" + +#include <imgui.h> + +#include <GuiCore/Timer.hpp> using namespace QPong; @@ -7,7 +20,7 @@ double QPong::posAt(int pos, int arraySize) return 2.0 * pos / arraySize - 1.0; } -double QPong::xAtIndex(int i, int arraySize) +double QPong::xAtIndex(int i, int arraySize) { return posAt(i % arraySize, arraySize); } @@ -17,14 +30,14 @@ double QPong::yAtIndex(int i, int arraySize) return posAt(i / arraySize, arraySize); } - double QPong::potentialAt(int pos, int arraySize, double hbar) { if (pos > arraySize / 2) { pos -= arraySize; } - return pos * M_PI * hbar; + constexpr double pi{3.14159265358979323846264}; + return pos * pi * hbar; } double QPong::pxAtIndex(int i, int arraySize, double hbar) @@ -36,3 +49,274 @@ double QPong::pyAtIndex(int i, int arraySize, double hbar) { return potentialAt(i / arraySize, arraySize, hbar); } + +Particle::Particle(Properties properties, GameOptions &options, Player &playerOne, Player &playerTwo, Bat &leftBat, Bat &rightBat, float *staticBorders, float *dynamicPotential) + : m_properties{properties} + , m_options{options} + , m_playerOne{playerOne} + , m_playerTwo{playerTwo} + , m_leftBat{leftBat} + , m_rightBat{rightBat} + , m_staticBorders{staticBorders} + , m_dynamicPotential{dynamicPotential} +{ + Log::get()->debug("Init Particle"); + m_xAt = new double[m_properties.elements()]; + m_yAt = new double[m_properties.elements()]; + m_E_At = new double[m_properties.elements()]; + + for (int i{0}; i < m_properties.elements(); ++i) + { + m_xAt[i] = xAtIndex(i, m_properties.size); + m_yAt[i] = yAtIndex(i, m_properties.size); + m_E_At[i] = (pow2(pxAtIndex(i, m_properties.size, m_properties.hbar)) + pow2(pyAtIndex(i, m_properties.size, m_properties.hbar))) / (2.0 * 1.0); + } + + int memorySize = sizeof(fftw_complex) * m_properties.elements(); + m_psi = static_cast<std::complex<double> *>(fftw_malloc(memorySize)); + m_momentum = static_cast<std::complex<double> *>(fftw_malloc(memorySize)); + initialiseParticleMomentum(); + calculateBorderPotential(m_staticBorders, m_yAt, m_properties.elements()); + + fftw_init_threads(); + fftw_plan_with_nthreads(m_properties.threads); + m_planForward = fftw_plan_dft_2d(m_properties.size, m_properties.size, (fftw_complex *)m_psi, (fftw_complex *)m_psi, FFTW_FORWARD, FFTW_MEASURE); + fftw_plan_with_nthreads(m_properties.threads); + m_planBackwards = fftw_plan_dft_2d(m_properties.size, m_properties.size, (fftw_complex *)m_psi, (fftw_complex *)m_psi, FFTW_BACKWARD, FFTW_MEASURE); + + int numberOfThreads = m_properties.threads; + for (int i = 0; i < numberOfThreads; i++) + { + const int from = i * (m_properties.elements() / numberOfThreads); + int to = (i+1) * (m_properties.elements() / numberOfThreads); + if (i+1 == numberOfThreads) + { + to = m_properties.elements(); + } + m_applyPotentialWorkers.emplace_back(std::make_unique<WorkerApplyPotential>(this, from, to)); + m_moveForwardWorkers.emplace_back(std::make_unique<WorkerMoveForward>(this, from, to)); + } + + Log::get()->debug("Init Particle with {} elements done.", m_properties.elements()); +} + +Particle::~Particle() +{ + Log::get()->debug("Deleting Particle"); + for (auto &worker : m_applyPotentialWorkers) + { + worker->quit(); + } + + for (auto &worker : m_moveForwardWorkers) + { + worker->quit(); + } + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + + fftw_free(m_psi); + delete (m_xAt); + delete (m_yAt); + delete (m_E_At); + delete (m_momentum); +} + +std::complex<double> *Particle::getPsi() const +{ + return m_psi; +} + +std::complex<double> *Particle::getMomentum() const +{ + return m_momentum; +} + +int Particle::arrayElementCount() const +{ + return pow2(m_properties.size); +} + +void Particle::initialiseParticleMomentum() +{ + constexpr std::complex<double> ci{0.0, 1.0}; // complex number + const double hbar = m_properties.hbar; + const double A0 = m_properties.startValue; + const double px0 = m_properties.momentum.x / 20.0; + const double py0 = m_properties.momentum.y / 20.0; + const double x0 = m_properties.position.x; + const double y0 = m_properties.position.y; + const double lambda = m_properties.pointUnsharpness; + + double sum{0.0}; + double m_particleAbsoluteSumStart = 0.0; + double y{0.0}; + double x{0.0}; + for (auto i{0}; i < arrayElementCount(); ++i) + { + x = m_xAt[i]; + y = m_yAt[i]; + m_psi[i] = A0 * exp(ci / hbar * (x * px0 + y * py0) - lambda * (pow2(x - x0) + pow2(y - y0))); + sum += pow2(std::real(m_psi[i])) + pow2(std::imag(m_psi[i])); + } + for (auto i{0}; i < arrayElementCount(); ++i) + { + m_psi[i] *= 10.0 / sqrt(sum); + m_particleAbsoluteSumStart += pow2(std::real(m_psi[i])) + pow2(std::imag(m_psi[i])); + } + Log::get()->trace("Particle initialised"); +} + +double Particle::absorbParticle(int i, double c) +{ + double normPrevious = pow2(std::real(m_psi[i])) + pow2(std::imag(m_psi[i])); + double absorb = pow2(std::cos(c * 10)); + m_psi[i] *= absorb; + double delta = normPrevious - (pow2(std::real(m_psi[i])) + pow2(std::imag(m_psi[i]))); + return delta; +} + +void Particle::applyPotentials(int indexBegin, int indexEnd, double dt) +{ + constexpr std::complex<double> ci{0.0, 1.0}; + const double hbar = m_properties.hbar; + double x{0.0}; + double y{0.0}; + const double potential = 1.0; + double particleAbsorbtAtPlayerOne = 0.0; + double particleAbsorbtAtPlayerTwo = 0.0; + + for (int i{indexBegin}; i < indexEnd; ++i) + { + double potSum = 1.0; + if (m_options.potentialsEnabled) + { + potSum = 0.0; + x = m_xAt[i]; + y = m_yAt[i]; + if (m_options.absorbtionEnabled) + { + if (x < m_leftBat.getX()) + { + double distanceToEdge = -m_leftBat.getX() + x; + double c = distanceToEdge * (M_PI / 2.0) / (1.0 + m_leftBat.getX()); + particleAbsorbtAtPlayerOne += absorbParticle(i, c); + } + if (x > m_rightBat.getX()) + { + double distanceToEdge = x - m_rightBat.getX(); + double c = distanceToEdge * (M_PI / 2.0) / (1.0 + m_rightBat.getX()); + particleAbsorbtAtPlayerTwo += absorbParticle(i, c); + } + } + double potLeftBat = m_leftBat.getPotential(x, y); + double potRightBat = m_rightBat.getPotential(x, y); + potSum = potLeftBat + potRightBat + m_staticBorders[i]; + } + auto psi = m_psi[i] * exp(-ci * dt / hbar * potential * potSum); + m_psi[i] = psi; + if (m_options.potentialsEnabled) + { + m_dynamicPotential[i] = potSum; + } + } + m_playerOne.addScore(particleAbsorbtAtPlayerTwo); + m_playerTwo.addScore(particleAbsorbtAtPlayerOne); +} + +void Particle::moveForward(int indexBegin, int indexEnd, double dt) +{ + constexpr std::complex<double> ci{0.0, 1.0}; + const double hbar = m_properties.hbar; + const double N = 1.0 / static_cast<double>(arrayElementCount()); + for (int i{indexBegin}; i < indexEnd; ++i) + { + m_psi[i] *= exp(-ci * dt / hbar * m_E_At[i]) * N; + } +} + +void Particle::updateMomentumView(int indexBegin, int indexEnd) +{ + GuiCore::Timer timer("updateMomentumView"); + int x{0}; + int y{0}; + for (int i{indexBegin}; i < indexEnd; ++i) + { + x = i % m_properties.size; + y = i / m_properties.size; + auto halfArraySize = m_properties.size / 2; + + if (y > halfArraySize) + { + y = y - halfArraySize; + } + else + { + y = y + halfArraySize; + } + + if (x > halfArraySize) + { + x = x - halfArraySize; + } + else + { + x = x + halfArraySize; + } + m_momentum[i] = m_psi[(y * m_properties.size) - x] * 100.0; + } +} + +void Particle::runWorkers(std::vector<std::unique_ptr<WorkerParticle>> &workers, double dt) +{ + for (auto &worker : workers) + { + worker->start(dt); + } + + bool working {true}; + while (working) + { + working = false; + for (auto &worker : workers) + { + if (worker->isWorking()) + { + std::this_thread::yield(); + working = true; + break; + } + } + } +} + + +void Particle::propagateStep(double dt) +{ + runWorkers(m_applyPotentialWorkers, dt); + + GuiCore::Timer timerFFT {"",false}; + m_timeFFT = 0.0; + fftw_execute(m_planForward); + m_timeFFT = timerFFT.stop(); + + runWorkers(m_moveForwardWorkers, dt); + + if (m_options.momentumEnabled) + { + GuiCore::Timer timer{"momentum view update"}; + updateMomentumView(0, m_properties.elements()); + } + timerFFT.start(); + fftw_execute(m_planBackwards); + m_timeFFT += timerFFT.stop(); +} + +void Particle::update(double dt) +{ + propagateStep(dt); +} + +double Particle::getTimeFFT() const +{ + return m_timeFFT; +} \ No newline at end of file diff --git a/QPong/Particle.hpp b/QPong/Particle.hpp index 31bef370fd7951c6542d23cde79fbd5286b3c20c..f5340eaf7e4df6c7588fa25f3304cd46d30b3d7a 100644 --- a/QPong/Particle.hpp +++ b/QPong/Particle.hpp @@ -1,16 +1,96 @@ +/// @file Particle.hpp +/// @author Armin Co +/// #ifndef QPONG_PARTICLE_HPP #define QPONG_PARTICLE_HPP +#include <complex> +#include <vector> +#include <memory> + +#include <fftw3.h> +#include <glm/glm.hpp> + +#include "Bat.hpp" +#include "Player.hpp" namespace QPong { - + class WorkerParticle; + double posAt(int pos, int arraySize); double xAtIndex(int i, int arraySize); double yAtIndex(int i, int arraySize); double potentialAt(int pos, int arraySize, double hbar); double pxAtIndex(int i, int arraySize, double hbar); double pyAtIndex(int i, int arraySize, double hbar); + + class Particle + { + public: + struct Properties + { + int size; + glm::vec3 position; + glm::vec3 momentum; + double pointUnsharpness; + double startValue; + double hbar; + int threads; + const int elements() const {return size * size;} + }; + + struct GameOptions + { + bool potentialsEnabled; + bool absorbtionEnabled; + bool momentumEnabled; + }; + + Particle(Properties properties, GameOptions &options, Player &m_playerOne, Player &m_playerTwo ,Bat &leftBat, Bat &rightBat, float *staticBorders, float *dynamicPotential); + + ~Particle(); + + void update(double dt); + + std::complex<double> *getPsi() const; + std::complex<double> *getMomentum() const; + + void applyPotentials(int indexBegin, int indexEnd, double dt); + void moveForward(int indexBegin, int indexEnd, double dt); + + double getTimeFFT() const; + private: + void initialiseParticleMomentum(); + void propagateStep(double dt); + void updateMomentumView(int indexBegin, int indexEnd); + double absorbParticle(int i, double c); + + int arrayElementCount() const; + + void runWorkers(std::vector<std::unique_ptr<WorkerParticle>> &workers, double dt); + + const Properties m_properties; + GameOptions &m_options; + Player &m_playerOne; + Player &m_playerTwo; + Bat &m_leftBat; + Bat &m_rightBat; + + std::vector<std::unique_ptr<WorkerParticle>> m_applyPotentialWorkers; + std::vector<std::unique_ptr<WorkerParticle>> m_moveForwardWorkers; + + fftw_plan m_planForward; + fftw_plan m_planBackwards; + double *m_xAt; + double *m_yAt; + double *m_E_At; + float *m_staticBorders; + float *m_dynamicPotential; + std::complex<double> *m_psi; + std::complex<double> *m_momentum; + double m_timeFFT; + }; } #endif \ No newline at end of file diff --git a/QPong/Worker.hpp b/QPong/Worker.hpp new file mode 100644 index 0000000000000000000000000000000000000000..730936c5a3ffcd87223c6328b26b7a111136f836 --- /dev/null +++ b/QPong/Worker.hpp @@ -0,0 +1,63 @@ +/// @file Workers.hpp +/// @author Armin Co +/// +/// @brief A collection of workers wich can work in parallel on the particle array. +/// + +#ifndef QPONG_WORKER_HPP +#define QPONG_WORKER_HPP + +#include <thread> +#include <atomic> +#include <memory> + +#include "Particle.hpp" + +namespace QPong +{ + class WorkerParticle + { + public: + WorkerParticle(Particle *particle, int startIndex, int stopIndex); + bool isWorking() const; + virtual void start(double dt); + void quit(); + + protected: + virtual void run() = 0; + + Particle *m_particle; + int m_startIndex; + int m_stopIndex; + double m_currentDt; + std::atomic_bool m_running; + std::thread m_workingThread; + std::atomic_bool m_isWorking; + }; + + class WorkerApplyPotential : public WorkerParticle + { + public: + WorkerApplyPotential(Particle *particle, int startIndex, int stopIndex) + : WorkerParticle(particle, startIndex, stopIndex) { + m_workingThread = std::thread(&WorkerApplyPotential::run, this); + m_workingThread.detach(); + } + private: + virtual void run() override; + }; + + class WorkerMoveForward : public WorkerParticle + { + public: + WorkerMoveForward(Particle *particle, int startIndex, int stopIndex) + : WorkerParticle(particle, startIndex, stopIndex) { + m_workingThread = std::thread(&WorkerMoveForward::run, this); + m_workingThread.detach(); + } + private: + virtual void run() override; + }; +} + +#endif \ No newline at end of file diff --git a/QPong/Workers.cpp b/QPong/Workers.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b8e7284adc90f94fdc3e39e3e67019cdbc670a66 --- /dev/null +++ b/QPong/Workers.cpp @@ -0,0 +1,68 @@ +/// @file Worker.cpp +/// @author Armin Co +/// + +#include "Worker.hpp" +#include <chrono> + +using namespace QPong; + +WorkerParticle::WorkerParticle(Particle *particle, int startIndex, int stopIndex) + : m_particle {particle} + , m_startIndex {startIndex} + , m_stopIndex {stopIndex} + , m_currentDt {0.0} + , m_isWorking {false} + , m_running {true} +{ +} + +bool WorkerParticle::isWorking() const +{ + return m_isWorking; +} + +void WorkerParticle::start(double dt) +{ + m_currentDt = dt; + m_isWorking = true; +} + +void WorkerParticle::quit() +{ + m_running = false; +} + + +void WorkerApplyPotential::run() +{ + while (m_running && (m_particle!=nullptr)) + { + if (m_isWorking) + { + m_particle->applyPotentials(m_startIndex, m_stopIndex, m_currentDt); + m_isWorking = false; + } + std::this_thread::sleep_for(std::chrono::microseconds(2000)); + // If there are other tasks on the system to run + // than run them now. + std::this_thread::yield(); + } +} + + +void WorkerMoveForward::run() +{ + while (m_running && (m_particle!=nullptr)) + { + if (m_isWorking) + { + m_particle->moveForward(m_startIndex, m_stopIndex, m_currentDt); + m_isWorking = false; + } + std::this_thread::sleep_for(std::chrono::microseconds(2000)); + // If there are other tasks on the system to run + // than run them now. + std::this_thread::yield(); + } +} \ No newline at end of file