#include "renderer_polygon_3d.hpp"

#include <iostream>

#include "state/events/explosion_event.hpp"
#include "state/events/missile_event.hpp"
#include "state/events/ship_event.hpp"

#include "developer_console.hpp"

namespace endofthejedi {
void RendererPolygon3d::setup() {
        m_lastTime = -1.0;

    //std::cout << "setup polygon 3d" << std::endl;

        m_shader_game_objects.init();
    m_shader_game_objects.loadFile("../data/shader/gameobjects.vert",
                                   GL_VERTEX_SHADER);
    m_shader_game_objects.loadFile("../data/shader/gameobjects.frag",
                                   GL_FRAGMENT_SHADER);

        m_shader_particles.init();
    m_shader_particles.loadFile("../data/shader/particle.vert",
                                GL_VERTEX_SHADER);
    m_shader_particles.loadFile("../data/shader/particle.frag",
                                GL_FRAGMENT_SHADER);

        m_shader_background.init();
    m_shader_background.loadFile("../data/shader/background.vert",
                                 GL_VERTEX_SHADER);
    m_shader_background.loadFile("../data/shader/background.frag",
                                 GL_FRAGMENT_SHADER);

        //addModel("../data/mesh/small_atomic_bomb.stl", &m_missileModel);
    addModel("../data/mesh/rocket.stl", &m_missileModel);
        addModel("../data/mesh/planet_128.stl", &m_planetModel);
    addModel("../data/mesh/ship_ufo.stl", &m_shipModel);

        m_texture = nullptr;
        m_backgroundTexturePath = "../data/img/background_3.png";
        loadBackgroundTexture();

    m_postprocess_shader.init();
    m_postprocess_shader.loadFile("../data/shader/postprocess.vert",
                                  GL_VERTEX_SHADER);
    m_postprocess_shader.loadFile("../data/shader/postprocess.frag",
                                  GL_FRAGMENT_SHADER);

    m_multitexture_shader.init();
    m_multitexture_shader.loadFile("../data/shader/multitexture.vert",
                                   GL_VERTEX_SHADER);
    m_multitexture_shader.loadFile("../data/shader/multitexture.frag",
                                   GL_FRAGMENT_SHADER);
    m_postprocess_fbo.init();
    m_postprocess_tex0.init();
    m_postprocess_tex1.init();

    developer::DeveloperConsole::instance().addCallback(
        "reload_bg", [=](const std::string &) {
            loadBackgroundTexture();
            return developer::resultOkay();
        });

    developer::DeveloperConsole::instance().addCallback(
        "set_bg", [=](const std::string &str) {
            std::string token;
            std::string rest;
            util::splitIntoTokenAndRest(str, token, rest);

            m_backgroundTexturePath = token;
            loadBackgroundTexture();
            return developer::resultOkay();
        });
    }

    void RendererPolygon3d::loadBackgroundTexture()
    {
        //m_texture = new ImageTexture("../data/img/test.png");
        if (m_texture != nullptr) {
            delete(m_texture);
        }

                //"../data/img/stars_nebular.png");
        m_texture = new ImageTexture(m_backgroundTexturePath);

        glActiveTexture(GL_TEXTURE0);
        m_texture->loadPng();

        //std::cout<<"texture loading: " << m_texture->valid() << std::endl;
        if (!m_texture->valid()) {
            std::cout<<"loading failed!";
            return;
            //exit(-1);
        }

        //glm::vec2 s = m_texture->size();
        //std::cout<<"texture size is " << s.x << " X " << s.y << std::endl;
    }

    void RendererPolygon3d::renderBackgroundImage()
    {
        m_shader_background.bind();

        glUniform2fv(m_shader_background.location("uvScale"), 1, glm::value_ptr(m_texture->uvScale()));

        glActiveTexture(GL_TEXTURE0);
        glEnable(GL_TEXTURE_2D);
        glBindTexture(GL_TEXTURE_2D, m_texture->textureId());

        //drawNdcQuad();
        glColor3f(1.0, 0.0, 0.0);
        glBegin(GL_QUADS);
        glVertex2f(-1.0f, -1.0f);
        glVertex2f(1.0f, -1.0f);
        glVertex2f(1.0f, 1.0f);
        glVertex2f(-1.0f, 1.0f);
        glEnd();
    }

    void RendererPolygon3d::render(const game::State *state)
    {
        if (m_lastTime == -1.0) {
            m_lastTime = state->timestamp();
        }

        float dt = state->timestamp() - m_lastTime;
        if (dt < 0.0) {
            dt = 0.0;
        }

        m_state = state;
        advanceGraphicObjects(dt);

        // TODO: add stars (texture)
        // TODO: add dust particles
        // TODO: add little rocks flying around

        glClearColor(0.0, 0.0, 0.0, 1.0);
        //glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    m_postprocess_fbo.bind();

    m_postprocess_tex0.bind();
    m_postprocess_tex0.fill(0, 3, m_w, m_h, 0, GL_RGB, GL_FLOAT, NULL);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

    m_postprocess_tex1.bind();
    m_postprocess_tex1.fill(0, 3, m_w, m_h, 0, GL_RGB, GL_FLOAT, NULL);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

    m_postprocess_fbo.attachTexture(GL_COLOR_ATTACHMENT0,
                                    m_postprocess_tex0.getName());
        renderBackgroundImage();

        //float s = 0.1;
        //glClearColor(s, s, 1.2*s, 1.0);

        m_shader_game_objects.bind();

        // TODO: add ONE sun planet
        // TODO: add lights for explosions

        configureLightningInShader(&m_shader_game_objects);

        //std::cout<<"setting aspect ratio: " << m_aspectRatio << std::endl;

        renderPlanets();
        renderShips();
        renderMissiles();

        renderParticles();

        renderTraces();

    glUniform1f(m_shader_game_objects.location("aspectRatio"), m_aspectRatio);
    m_postprocess_tex1.bind();
    m_postprocess_fbo.attachTexture(GL_COLOR_ATTACHMENT0,
                                    m_postprocess_tex1.getName());
    //glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    m_shader_game_objects.bind();
    configureLightningInShader(&m_shader_game_objects);
    glUniform1f(m_shader_game_objects.location("aspectRatio"), m_aspectRatio);
    renderPlanets();
    renderShips();
    renderMissiles();

    renderParticles();

    renderTraces();

    // postprocessing
    m_postprocess_shader.bind();
        //glColor3f(1.0, 0.0, 0.0);
    glActiveTexture(GL_TEXTURE0);
    m_postprocess_tex1.bind();

    glUniform1i(m_postprocess_shader.location("tex"), 0);
    glUniform2f(m_postprocess_shader.location("uvScale"), 1, 1);

    for (int i = 0; i < 2; i++) {
        glUniform2f(m_postprocess_shader.location("scale"), 2.0f / m_w,
                    0.0f);
        //glVertex2f(1.0f, -1.0f);
        glBegin(GL_QUADS);
        glVertex2f(-1.0f, -1.0f);
        glVertex2f(1.0f, -1.0f);
        glVertex2f(1.0f, 1.0f);
        glVertex2f(-1.0f, 1.0f);
        glEnd();
        //glVertex2f(1.0f, 1.0f);
        glUniform2f(m_postprocess_shader.location("scale"), 0.0f,
                    2.0f / m_h);
        //glVertex2f(-1.0f, 1.0f);
        glBegin(GL_QUADS);
        glVertex2f(-1.0f, -1.0f);
        glVertex2f(1.0f, -1.0f);
        glVertex2f(1.0f, 1.0f);
        glVertex2f(-1.0f, 1.0f);
        glEnd();
    }

    m_postprocess_fbo.unbind();

    // multitexturing (final step)
    m_multitexture_shader.bind();

    glUniform1i(m_multitexture_shader.location("tex0"), 0);
    glUniform1i(m_multitexture_shader.location("tex1"), 1);
    glUniform2f(m_multitexture_shader.location("uvScale"), 1, 1);
    glUniform2f(m_multitexture_shader.location("scale"), 0.2f / 1000.0f, 0.0f);

    glActiveTexture(GL_TEXTURE0);
    m_postprocess_tex0.bind();
    glActiveTexture(GL_TEXTURE1);
    m_postprocess_tex1.bind();
        //glEnd();
    glColor3f(1.0, 0.0, 0.0);
    glBegin(GL_QUADS);
    glVertex2f(-1.0f, -1.0f);
    glVertex2f(1.0f, -1.0f);
    glVertex2f(1.0f, 1.0f);
    glVertex2f(-1.0f, 1.0f);
    glEnd();

        m_lastTime = state->timestamp();
    }

    void RendererPolygon3d::renderParticles()
    {
        m_shader_particles.bind();

        glUniform1f(m_shader_particles.location("aspectRatio"), m_aspectRatio);

        for (ParticleBatch *batch : m_particles) {
            batch->bind();
            batch->render(&m_shader_particles);
        }
    }

    void RendererPolygon3d::addExplosionEffect(
            size_t id, const glm::vec2 &explCenter, const glm::vec2 &missileVelocity,
            bool isPlanetHit,
            size_t n, float duration)
    {
        //float particleRadius = 0.005;
        //float particleRadius = 0.003;
        float particleRadius = 0.02;

        // TODO: use this as shader input too and make the area 2x around this
        // so that it stays hot/yellow for 2/3 of the time
        float explCoreSize = 0.02f;
        float maxVelocity  = 0.4f;

        ParticleBatch *batch = new ParticleBatch(id, n, particleRadius, duration);
        batch->setup(&m_shader_particles);
        batch->setCenter(glm::vec3(explCenter, 0.0));
        batch->setMaxVelocity(maxVelocity);

        for (size_t i=0; i<n; i++) {
            // distribute in a circle
            //float t = 2.0 * M_PI * i / (float) n;
            //t += 0.2*util::randf_m1_1();

            // with random velocities
            // this is 3d because it looks better if some particles leave/enter
            // the space and they are not all on one plane.
            // especially in 3d this would look bad without 3d velocity vector.
            //glm::vec3 v = 0.5f*glm::vec3(sin(t), cos(t), util::randf_m1_1());

            glm::vec3 pos = glm::vec3(explCenter, 0.0) + glm::ballRand(explCoreSize);

            glm::vec3 v = glm::ballRand(maxVelocity);

            // TODO: is that good?
            if (isPlanetHit) {
                v *= util::randf_0_1() * util::randf_0_1() * util::randf_0_1();
            } else {
                v *= util::randf_0_1();
            }

            // find collisions with planetns and limit max distance so particles
            // won't fly through planets
            //float maxDist = 0.1;
            //float maxDist = 0.1*util::randf_0_1();
            bool isInsidePlanet = false;
            float maxParticleDist = INFINITY;

            const game::Planet *nearestPlanet = nullptr;
            for (const game::Planet *planet : m_state->planets) {
                const glm::vec3 ppos3 = glm::vec3(planet->position, 0.0f);

                // TODO: that's slightly wrong. use intersection for this.
                float dist = glm::distance(ppos3, pos);
                if (dist <= planet->radius) {
                    isInsidePlanet = true;
                    nearestPlanet = planet;
                }
                if (isInsidePlanet) {
                    // skip searching for nearer planets once we are inside some
                    // planet as the position/velocity will be changed to start
                    // at the surface of the planet we were in with
                    // reflected or planet-normal velocity.
                    continue;
                }
                // TODO: if inside, move position so that it looks like
                // reflecting the particle from the planet
                bool fliesInPlanetDirection = glm::dot(v, ppos3-pos) > 0.0f;
                if (dist < maxParticleDist && fliesInPlanetDirection) {
                    nearestPlanet = planet;
                    maxParticleDist = dist;
                }
            }

            bool makeStationary = false;

            if (isInsidePlanet && isPlanetHit) {
                util::IntersectionTest intersect;
                if (!intersect.raySphere(
                    glm::vec3(explCenter, 0.0f), v,
                    glm::vec3(nearestPlanet->position, 0.0f), nearestPlanet->radius))
                {
                    makeStationary = true;
                    //std::cout<<"warning: intersection should be valid!" << std::endl;
                    // TODO: must be as they lie on a plane and the dist is < as
                    // the radius.
                    // handle if this is wrong.

                } else {
                    // simple reflection, ignoring the missile velocity: this looks good enough
                    glm::vec3 planetNormal = glm::normalize(pos - glm::vec3(nearestPlanet->position, 0.0f));
                    v = glm::length(v) * planetNormal;

                    // TODO
                    // considering the missile velocity is not yet working:

                    // set position to the intersection point between explosion
                    // center and planet surface
                    //pos = intersect.pointAtDistance(intersect.distance());
                    //v = glm::vec3(missileVelocity, 0.0f);
                    //v = v - 2.0f*glm::dot(v, planetNormal) * planetNormal;
                    //v *= 4.0;
                    //maxParticleDist = 100.0;
                    //v = -v;

                    //pos = glm::vec3(nearestPlanet->position, 0.0f) + nearestPlanet->radius*planetNormal;

                    // set position to the intersection point between explosion
                    // center and planet surface
                    //const glm::vec3 planetNormal = glm::vec3(glm::normalize(explCenter - nearestPlanet->position), 0.0f);

                    //pos = glm::vec3(nearestPlanet->position, 0.0f) + nearestPlanet->radius*planetNormal;

                    // build new velocity by reflecting the old velocity on the
                    // planet normal
                    // TODO: add a bit random
                    // TODO: add reflection
                    // TODO: distribute particles around main reflection angle and
                    // TODO: add material exhaust that is specific for the planet.
                    // TODO: spawn waves on water planet
                    // TODO: start fire on gas planet
                    //v = glm::length(v) * planetNormal;
                    //v = v - 2.0f*glm::dot(v, planetNormal) * planetNormal;
                    //v = glm::length(v) * planetNormal;

                    //glm::vec3 r  = v - 2.0f*glm::dot(v, planetNormal) * planetNormal;
                    //glm::vec3 vn = glm::length(v) * planetNormal;
                    //v = (r+vn) / 2.0f;

                    //glm::vec3 vc = glm::vec3(nearestPlanet->position-explCenter, 0.0f);
                    //glm::vec3 r  = vc - 2.0f*glm::dot(vc, planetNormal) * planetNormal;
                    //v = r;
                }
            } else if (isInsidePlanet && !isPlanetHit) {
                // if a planet is just hit by explosions particles but not the
                // missile itself, don't reflect the particles in the planet.
                // just set them as stationary at place of explosion
                makeStationary = true;
            }

            if (makeStationary) {
                v = glm::vec3(0.0f, 0.0f, 0.0f);
                pos = glm::vec3(explCenter, 0.0f);;
            }

            batch->setParticle(i, pos, v, maxParticleDist);
        }

        batch->upload();

        m_particles.push_back(batch);
    }

    void RendererPolygon3d::advanceGraphicObjects(float dt)
    {
#if 0
        for (const game::Explosion *expl : m_state->explosions) {
            bool gotIt = false;
            for (ParticleBatch *batch : m_particles) {
                if (batch->id() == expl->id) {
                    gotIt = true;
                    break;
                }
            }

            if (!gotIt) {
                addExplosionEffect(
                        expl->id, expl->position,
                        glm::vec3(expl->missileVelocity,1.0),
                        (expl->hit == game::Hit::Planet),
                        1000, 1.0);
            }
        }
#endif

        for (auto *evt : m_state->currentStateUpdateEvents()) {
            auto type  = evt->eventType();
            auto cycle = evt->lifeCycle();

            if (type == game::EventType::Explosion) {
                if (cycle == game::LifeCycle::Create) {
                    game::ExplosionEvent *ee = static_cast<game::ExplosionEvent*>(evt);
                    game::Explosion *expl    = static_cast<game::Explosion*>(ee->object());

                    addExplosionEffect(
                            expl->id, expl->position,
                            glm::vec3(expl->missileVelocity,1.0),
                            (expl->hit == game::Hit::Planet),
                            1000, 1.0);
                }
            } else if (type == game::EventType::Ship) {
                game::ShipEvent *me = static_cast<game::ShipEvent*>(evt);
                game::Ship *ship = static_cast<game::Ship*>(me->object());

                // is always modificated
                if (cycle == game::LifeCycle::Create) {
                    //std::cout<<"[renderer] adding missile #" << missile->id << std::endl;
                    m_ships.push_back(ship);

                } else if (cycle == game::LifeCycle::Destroy) {
                    //std::cout<<"[renderer] removing missile #" << missile->id << std::endl;
                    m_ships.remove(ship);
                }
            } else if (type == game::EventType::Missile) {
                game::MissileEvent *me = static_cast<game::MissileEvent*>(evt);
                game::Missile *missile = static_cast<game::Missile*>(me->object());

                // is always modificated
                if (cycle == game::LifeCycle::Create) {
                    //std::cout<<"[renderer] adding missile #" << missile->id << std::endl;
                    m_missiles.push_back(missile);

                } else if (cycle == game::LifeCycle::Destroy) {
                    //std::cout<<"[renderer] removing missile #" << missile->id << std::endl;
                    m_missiles.remove(missile);
                }
            }
        }

        //if (m_particles.size() == 0) {
        //    addExplosionEffect(0, glm::vec2(0.0, 0.0), glm::vec2(0.0, 0.0), false, 10000, 2.0);
        //}

        std::vector<ParticleBatch*> rm;

        for (ParticleBatch *batch : m_particles) {
            batch->tick(dt);
            if (batch->done()) {
                rm.push_back(batch);
            }
        }

        for (ParticleBatch *batch : rm) {
            m_particles.remove(batch);
        delete (batch);
        }
    }

void RendererPolygon3d::renderPlanets() {
        m_planetModel->bind();

        // TODO: put material into attributes and render witd glDrawInstanced
        // too (same for missiles)
        for (const game::Planet *planet : m_state->planets) {
            glm::mat4 model = computeModelMatrix(planet);
        glUniformMatrix4fv(m_shader_game_objects.location("model"), 1, GL_FALSE,
                           glm::value_ptr(model));

            glm::vec3 c = planet->getColor();
        glUniform3f(m_shader_game_objects.location("materialColor"), c.x, c.y,
                    c.z);
        glUniform1i(m_shader_game_objects.location("materialSeed"),
                    planet->seed);
        glUniform1i(m_shader_game_objects.location("materialKind"),
                    (int)planet->material);

            m_planetModel->render();
        }
    }
/*
void RendererPolygon3d::renderSun() {
    m_planetModel->bind();

    // TODO: put material into attributes and render witd glDrawInstanced
    // too (same for missiles)
    for (const game::Planet *planet : m_state->planets) {

        if (planet->material != game::Planet::Material::Sun)
            continue;

        glm::mat4 model = computeModelMatrix(planet);
        glUniformMatrix4fv(m_shader_game_objects.location("model"), 1, GL_FALSE,
                           glm::value_ptr(model));

        glm::vec3 c = planet->getColor();
        glUniform3f(m_shader_game_objects.location("materialColor"), c.x, c.y,
                    c.z);
        glUniform1i(m_shader_game_objects.location("materialSeed"),
                    planet->seed);
        glUniform1i(m_shader_game_objects.location("materialKind"),
                    (int)planet->material);

        m_planetModel->render();
    }
}
*/
void RendererPolygon3d::renderMissiles() {
        // TODO: add fire trail for missiles near the sun

        m_missileModel->bind();

        for (const game::Missile *missile : m_missiles) {
            glm::vec3 c = glm::vec3(1.0, 1.0, 0.3);
            glUniform3f(m_shader_game_objects.location("materialColor"), c.x, c.y, c.z);

            // TODO: rename functions so their name represents what args they
            // take
            glm::mat4 model = computeModelMatrix(missile);
            glUniformMatrix4fv(m_shader_game_objects.location("model"), 1, GL_FALSE, glm::value_ptr(model));

            m_missileModel->render();
        }
    }

    void RendererPolygon3d::renderShips()
    {
        m_shipModel->bind();

        for (const game::Ship *ship : m_ships) {
            glm::mat4 model = computeModelMatrix(ship);
            glUniformMatrix4fv(m_shader_game_objects.location("model"), 1, GL_FALSE, glm::value_ptr(model));

            glm::vec3 c = ship->color;
            glUniform3f(m_shader_game_objects.location("materialColor"), c.x, c.y, c.z);

            m_shipModel->render();
        }
    }

    void RendererPolygon3d::addModel(const std::string &filename, PolygonModel **dest)
    {
        //std::cout<<"adding a model: " << filename << std::endl;

        *dest = new PolygonModel(filename);
        if (!(*dest)->import()) {
        std::cout << "error: failed to load needed model!!!" << std::endl
                  << std::endl;
        exit(-1);
    }

    (*dest)->setup(&m_shader_game_objects);
    (*dest)->uploadToOpenGl();

    *dest = new PolygonModel(filename);
    if (!(*dest)->import()) {
        std::cout << "error: failed to load needed model!!!" << std::endl
                  << std::endl;
            exit(-1);
        }

        (*dest)->setup(&m_shader_game_objects);
        (*dest)->uploadToOpenGl();

        m_models.push_back(*dest);
    }

    glm::mat4 RendererPolygon3d::computeModelMatrix(const game::Planet *planet)
    {
        return computeModelMatrix(planet->position, planet->radius);
    }

    glm::mat4 RendererPolygon3d::computeModelMatrix(const game::Missile *missile)
    {
        glm::vec2 vn = glm::normalize(missile->velocity);
        float a = std::atan2(vn.y, vn.x);

        glm::mat4 mat = computeModelMatrix(missile->position, 0.1f, a);

        // TODO: which visual size has the rocket? in game its just a point with
        // no size because all others have size.
        //for atomic bomb
        //return computeModelMatrix(missile->position, 0.03f, a);

        // flipped too
        mat = glm::rotate(mat, (float) M_PI/2.0f, glm::vec3(0.0f, 1.0f, 0.0f));

        return mat;
    }

    glm::mat4 RendererPolygon3d::computeModelMatrix(const game::Ship *ship)
    {
        // TODO: rotate them before shooting, that looks better
        glm::mat4 mat = computeModelMatrix(ship->position, m_state->shipRadius());

        // XXX model is flipped
        //glm::mat4 mat = computeModelMatrix(ship->position, 0.3);
        mat = glm::rotate(mat, (float) M_PI, glm::vec3(0.0f, 1.0f, 0.0f));
        return mat;
    }

    glm::mat4 RendererPolygon3d::computeModelMatrix(const glm::vec2 &pos, float scale, float angle)
    {
        // init as identity matrix
        glm::mat4 model;

        model = glm::translate(model, glm::vec3(pos, 0.0));

        if (scale != 1.0) {
            model = glm::scale(model, glm::vec3(scale));
        }

        if (angle != 0.0) {
            model = glm::rotate(model, angle, glm::vec3(0.0f, 0.0f, 1.0f));
        }

        return model;
    }

    void RendererPolygon3d::renderTraces()
    {
        // revert to default
        glUseProgram(0);

        // TODO dont use line mode. make that with own quads
        glPolygonMode(GL_FRONT, GL_LINE);
        for (const game::Trace *trace : m_state->traces) {
            float fade_out = 1.0;
            if (trace->missile == nullptr) {
                fade_out = 1.0 - (trace->age / trace->maxAge);
            }

            glLineWidth(3);
            glColor4f(trace->player->color.x, trace->player->color.y, trace->player->color.z, fade_out);
            glBegin(GL_LINE_STRIP);
            for (const game::Trace::TracePoint &tp : trace->points) {
                glVertex2f(tp.position.x, tp.position.y);
            }
            glEnd();
        }
    glLineWidth(1.0f);
        glPolygonMode(GL_FRONT, GL_FILL);
    }

    void RendererPolygon3d::configureLightningInShader(Shader *shader) const
    {
        // TODO: add a few small lights for explosions so they lit the
        // surroundsings

        // TODO: use the sun planet color for this!
        glm::vec3 c = glm::vec3(1.0, 1.0, 0.8);
        glm::vec3 p = glm::vec3(0.3f, 0.4f, 0.0f);

        for (const game::Planet *planet : m_state->planets) {
            if (planet->material == game::Planet::Material::Sun) {
                p = glm::vec3(planet->position, 0.0);
                c = planet->getColor();
                break;
            }
        }

        glUniform3f(shader->location("lightPosition"), p.x, p.y, p.z);
        glUniform3f(shader->location("lightColor"),    c.x, c.y, c.z);

        std::vector<glm::vec3> positions;
        std::vector<float> intensities;

        size_t numExplLights = 0;

        for (ParticleBatch *batch : m_particles) {
            float age = batch->ageNormalized();
            // TODO: use function with a peak for this:
            //
            //   /\__
            // _/    \--____

            float intensity = 1.0-age;
            glm::vec3 p = batch->explosionCenter();

            intensities.push_back(intensity);
            positions.push_back(p);

            numExplLights++;

            if (numExplLights == 10) {
                break;
            }
        }

        glUniform1i( shader->location("explLightsNum"), numExplLights);

        if (numExplLights != 0) {
            glUniform3fv(shader->location("explLightsPos"),         numExplLights, glm::value_ptr(positions[0]));
            glUniform1fv(shader->location("explLightsIntensities"), numExplLights, intensities.data());
        }
    }

void RendererPolygon3d::setWindowSize(int px, int py) {
    m_aspectRatio = (float)px / (float)py;
    m_w = px;
    m_h = py;
    }

    void RendererPolygon3d::setCameraMatrix(const glm::mat4 &cam)
    {
        (void) cam;
    }
}
