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