/*
 * This file is part of the Colobot: Gold Edition source code
 * Copyright (C) 2001-2023, Daniel Roux, EPSITEC SA & TerranovaTeam
 * http://epsitec.ch; http://colobot.info; http://github.com/colobot
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see http://gnu.org/licenses
 */


#include "graphics/engine/particle.h"

#include "app/app.h"

#include "common/logger.h"

#include "graphics/core/device.h"

#include "graphics/engine/engine.h"
#include "graphics/engine/terrain.h"
#include "graphics/engine/text.h"
#include "graphics/engine/water.h"

#include "level/robotmain.h"

#include "math/geometry.h"

#include "object/object.h"
#include "object/object_manager.h"

#include "object/interface/damageable_object.h"

#include "object/subclass/shielder.h"

#include "sound/sound.h"

#include <cstring>


// Graphics module namespace
namespace Gfx
{


const float FOG_HSUP    = 10.0f;
const float FOG_HINF    = 100.0f;


//! Check if an object is a destroyable enemy
static bool IsAlien(ObjectType type)
{
    return ( type == OBJECT_ANT      ||
             type == OBJECT_SPIDER   ||
             type == OBJECT_BEE      ||
             type == OBJECT_WORM     ||
             type == OBJECT_MOTHER   ||
             type == OBJECT_NEST     ||
             type == OBJECT_BULLET   ||
             type == OBJECT_EGG      ||
             type == OBJECT_TEEN28   ||
             type == OBJECT_TEEN31   );
}

CParticle::CParticle(CEngine* engine)
    : m_engine(engine)
{
    std::fill_n(m_frameUpdate, SH_MAX, true);
}

CParticle::~CParticle()
{
}

void CParticle::SetDevice(CDevice* device)
{
    m_device = device;
}

void CParticle::FlushParticle()
{
    for (int i = 0; i < MAXPARTICULE*MAXPARTITYPE; i++)
        m_particle[i].used = false;

    for (int i = 0; i < MAXPARTITYPE; i++)
    {
        for (int j = 0; j < SH_MAX; j++)
        {
            m_totalInterface[i][j] = 0;
        }
    }

    for (int i = 0; i < MAXTRACK; i++)
        m_track[i].used = false;

    m_wheelTraceTotal = 0;
    m_wheelTraceIndex = 0;

    for (int i = 0; i < SH_MAX; i++)
        m_frameUpdate[i] = true;

    m_fogTotal = 0;
    m_exploGunCounter = 0;
}

void CParticle::FlushParticle(int sheet)
{
    for (int i = 0; i < MAXPARTICULE*MAXPARTITYPE; i++)
    {
        if (!m_particle[i].used) continue;
        if (m_particle[i].sheet != sheet) continue;

        m_particle[i].used = false;
    }

    for (int i = 0; i < MAXPARTITYPE; i++)
        m_totalInterface[i][sheet] = 0;

    for (int i = 0; i < MAXTRACK; i++)
        m_track[i].used = false;

    if (sheet == SH_WORLD)
    {
        m_wheelTraceTotal = 0;
        m_wheelTraceIndex = 0;
    }
}


//! Returns file name of the effect effectNN.png, with NN = number
static void NameParticle(std::string &name, int num)
{
         if (num == 1)  name = "effect00.png";
    else if (num == 2)  name = "effect01.png";
    else if (num == 3)  name = "effect02.png";
    else if (num == 4)  name = "effect03.png";
    else                name = "";
}

//! Returns random letter for use as virus particle
static char RandomLetter()
{
    static std::vector<char> chars;
    if (chars.empty())
    {
        // Add each special character once
        chars.push_back('{');
        chars.push_back('}');
        chars.push_back('(');
        chars.push_back(')');
        chars.push_back('<');
        chars.push_back('>');
        chars.push_back('+');
        chars.push_back('-');
        chars.push_back('=');
        chars.push_back('!');

        // Add each letter once
        for(char c = 'A'; c <= 'Z'; c++) chars.push_back(c);

        // Add each number 4 times
        for(char c = '0'; c <= '9'; c++) for(int i = 0; i < 4; i++) chars.push_back(c);
    }

    return chars[rand()%chars.size()];
}

/** Returns the channel of the particle created or -1 on error. */
int CParticle::CreateParticle(Math::Vector pos, Math::Vector speed, Math::Point dim,
                              ParticleType type,
                              float duration, float mass,
                              float windSensitivity, int sheet)
{
    if (m_main == nullptr)
        m_main = CRobotMain::GetInstancePointer();

    int t = -1;
    if ( type == PARTIEXPLOT   ||
         type == PARTIEXPLOO   ||
         type == PARTIMOTOR    ||
         type == PARTIBLITZ    ||
         type == PARTICRASH    ||
         type == PARTIVAPOR    ||
         type == PARTIGAS      ||
         type == PARTIBASE     ||
         type == PARTIFIRE     ||
         type == PARTIFIREZ    ||
         type == PARTIBLUE     ||
         type == PARTIROOT     ||
         type == PARTIRECOVER  ||
         type == PARTIEJECT    ||
         type == PARTISCRAPS   ||
         type == PARTIGUN2     ||
         type == PARTIGUN3     ||
         type == PARTIGUN4     ||
         type == PARTIQUEUE    ||
         type == PARTIORGANIC1 ||
         type == PARTIORGANIC2 ||
         type == PARTIFLAME    ||
         type == PARTIBUBBLE   ||
         type == PARTIERROR    ||
         type == PARTIWARNING  ||
         type == PARTIINFO     ||
         type == PARTISPHERE1  ||
         type == PARTISPHERE2  ||
         type == PARTISPHERE4  ||
         type == PARTISPHERE5  ||
         type == PARTISPHERE6  ||
         type == PARTIPLOUF0   ||
         type == PARTITRACK1   ||
         type == PARTITRACK2   ||
         type == PARTITRACK3   ||
         type == PARTITRACK4   ||
         type == PARTITRACK5   ||
         type == PARTITRACK6   ||
         type == PARTITRACK7   ||
         type == PARTITRACK8   ||
         type == PARTITRACK9   ||
         type == PARTITRACK10  ||
         type == PARTITRACK11  ||
         type == PARTITRACK12  ||
         type == PARTILENS1    ||
         type == PARTILENS2    ||
         type == PARTILENS3    ||
         type == PARTILENS4    ||
         type == PARTIGFLAT    ||
         type == PARTIDROP     ||
         type == PARTIWATER    ||
         type == PARTILIMIT1   ||
         type == PARTILIMIT2   ||
         type == PARTILIMIT3   ||
         type == PARTIEXPLOG1  ||
         type == PARTIEXPLOG2  )
    {
        t = 1;  // effect00
    }
    if ( type == PARTIGLINT   ||
         type == PARTIGLINTb  ||
         type == PARTIGLINTr  ||
         type == PARTITOTO    ||
         type == PARTISELY    ||
         type == PARTISELR    ||
         type == PARTIQUARTZ  ||
         type == PARTIGUNDEL  ||
         type == PARTICONTROL ||
         type == PARTISHOW    ||
         type == PARTICHOC    ||
         type == PARTIFOG4    ||
         type == PARTIFOG5    ||
         type == PARTIFOG6    ||
         type == PARTIFOG7    )
    {
        t = 2;  // effect01
    }
    if ( type == PARTIGUN1    ||
         type == PARTIFLIC    ||
         type == PARTISPHERE0 ||
         type == PARTISPHERE3 ||
         type == PARTIFOG0    ||
         type == PARTIFOG1    ||
         type == PARTIFOG2    ||
         type == PARTIFOG3    )
    {
        t = 3;  // effect02
    }
    if ( type == PARTISMOKE1  ||
         type == PARTISMOKE2  ||
         type == PARTISMOKE3  ||
         type == PARTIBLOOD   ||
         type == PARTIBLOODM  )
    {
        t = 4;  // effect03 (ENG_RSTATE_TTEXTURE_WHITE)
    }
    if ( type == PARTIVIRUS )
    {
        t = 5; // text render
    }
    if (t >= MAXPARTITYPE) return -1;
    if (t == -1) return -1;

    for (int j = 0; j < MAXPARTICULE; j++)
    {
        int i = MAXPARTICULE*t+j;

        if (! m_particle[i].used)
        {
            m_particle[i] = Particle();
            m_particle[i].used      = true;
            m_particle[i].ray       = false;
            m_particle[i].uniqueStamp = m_uniqueStamp++;
            m_particle[i].sheet     = sheet;
            m_particle[i].mass      = mass;
            m_particle[i].duration  = duration;
            m_particle[i].pos       = pos;
            m_particle[i].goal      = pos;
            m_particle[i].speed     = speed;
            m_particle[i].windSensitivity = windSensitivity;
            m_particle[i].dim       = dim;
            m_particle[i].zoom      = 1.0f;
            m_particle[i].angle     = 0.0f;
            m_particle[i].intensity = 1.0f;
            m_particle[i].type      = type;
            m_particle[i].phase     = PARPHSTART;
            m_particle[i].texSup.x  = 0.0f;
            m_particle[i].texSup.y  = 0.0f;
            m_particle[i].texInf.x  = 0.0f;
            m_particle[i].texInf.y  = 0.0f;
            m_particle[i].time      = 0.0f;
            m_particle[i].phaseTime = 0.0f;
            m_particle[i].testTime  = 0.0f;
            m_particle[i].objLink   = nullptr;
            m_particle[i].objFather = nullptr;
            m_particle[i].trackRank = -1;

            m_totalInterface[t][sheet] ++;

            if ( type == PARTIEXPLOT ||
                 type == PARTIEXPLOO )
            {
                m_particle[i].angle = Math::Rand()*Math::PI*2.0f;
            }

            if ( type == PARTIGUN1 ||
                 type == PARTIGUN4 )
            {
                m_particle[i].testTime = 1.0f;  // impact immediately
            }

            if ( type == PARTIVIRUS )
            {
                m_particle[i].text = RandomLetter();
            }

            if ( type >= PARTIFOG0 &&
                 type <= PARTIFOG7 )
            {
                if (m_fogTotal < MAXPARTIFOG)
                m_fog[m_fogTotal++] = i;
            }

            return i | ((m_particle[i].uniqueStamp&0xffff)<<16);
        }
    }

    return -1;
}

/** Returns the channel of the particle created or -1 on error */
int CParticle::CreateFrag(Math::Vector pos, Math::Vector speed,
                          EngineTriangle *triangle,
                          ParticleType type,
                          float duration, float mass,
                          float windSensitivity, int sheet)
{
    int t = 0;
    for (int j = 0; j < MAXPARTICULE; j++)
    {
        int i = MAXPARTICULE*t+j;

        if (!m_particle[i].used)
        {
            m_particle[i] = Particle();
            m_particle[i].used      = true;
            m_particle[i].ray       = false;
            m_particle[i].uniqueStamp = m_uniqueStamp++;
            m_particle[i].sheet     = sheet;
            m_particle[i].mass      = mass;
            m_particle[i].duration  = duration;
            m_particle[i].pos       = pos;
            m_particle[i].goal      = pos;
            m_particle[i].speed     = speed;
            m_particle[i].windSensitivity = windSensitivity;
            m_particle[i].zoom      = 1.0f;
            m_particle[i].angle     = 0.0f;
            m_particle[i].intensity = 1.0f;
            m_particle[i].type      = type;
            m_particle[i].phase     = PARPHSTART;
            m_particle[i].texSup.x  = 0.0f;
            m_particle[i].texSup.y  = 0.0f;
            m_particle[i].texInf.x  = 0.0f;
            m_particle[i].texInf.y  = 0.0f;
            m_particle[i].time      = 0.0f;
            m_particle[i].phaseTime = 0.0f;
            m_particle[i].testTime  = 0.0f;
            m_particle[i].objLink   = nullptr;
            m_particle[i].objFather = nullptr;
            m_particle[i].trackRank = -1;
            m_triangle[i] = *triangle;

            m_totalInterface[t][sheet] ++;

            Math::Vector    p1;
            p1.x = m_triangle[i].triangle[0].coord.x;
            p1.y = m_triangle[i].triangle[0].coord.y;
            p1.z = m_triangle[i].triangle[0].coord.z;

            Math::Vector p2;
            p2.x = m_triangle[i].triangle[1].coord.x;
            p2.y = m_triangle[i].triangle[1].coord.y;
            p2.z = m_triangle[i].triangle[1].coord.z;

            Math::Vector p3;
            p3.x = m_triangle[i].triangle[2].coord.x;
            p3.y = m_triangle[i].triangle[2].coord.y;
            p3.z = m_triangle[i].triangle[2].coord.z;

            float l1 = Math::Distance(p1, p2);
            float l2 = Math::Distance(p2, p3);
            float l3 = Math::Distance(p3, p1);
            float dx = fabs(Math::Min(l1, l2, l3))*0.5f;
            float dy = fabs(Math::Max(l1, l2, l3))*0.5f;
            p1 = Math::Vector(-dx,  dy, 0.0f);
            p2 = Math::Vector( dx,  dy, 0.0f);
            p3 = Math::Vector(-dx, -dy, 0.0f);

            m_triangle[i].triangle[0].coord.x = p1.x;
            m_triangle[i].triangle[0].coord.y = p1.y;
            m_triangle[i].triangle[0].coord.z = p1.z;

            m_triangle[i].triangle[1].coord.x = p2.x;
            m_triangle[i].triangle[1].coord.y = p2.y;
            m_triangle[i].triangle[1].coord.z = p2.z;

            m_triangle[i].triangle[2].coord.x = p3.x;
            m_triangle[i].triangle[2].coord.y = p3.y;
            m_triangle[i].triangle[2].coord.z = p3.z;

            Math::Vector n(0.0f, 0.0f, -1.0f);

            m_triangle[i].triangle[0].normal.x = n.x;
            m_triangle[i].triangle[0].normal.y = n.y;
            m_triangle[i].triangle[0].normal.z = n.z;

            m_triangle[i].triangle[1].normal.x = n.x;
            m_triangle[i].triangle[1].normal.y = n.y;
            m_triangle[i].triangle[1].normal.z = n.z;

            m_triangle[i].triangle[2].normal.x = n.x;
            m_triangle[i].triangle[2].normal.y = n.y;
            m_triangle[i].triangle[2].normal.z = n.z;

            if (type == PARTIFRAG)
                m_particle[i].angle = Math::Rand()*Math::PI*2.0f;

            return i | ((m_particle[i].uniqueStamp&0xffff)<<16);
        }
    }

    return -1;
}


/** Returns the channel of the particle created or -1 on error */
int CParticle::CreatePart(Math::Vector pos, Math::Vector speed,
                          ParticleType type,
                          float duration, float mass, float weight,
                          float windSensitivity, int sheet)
{
    int t = 0;
    for (int j = 0; j < MAXPARTICULE; j++)
    {
        int i = MAXPARTICULE*t+j;

        if (!m_particle[i].used)
        {
            m_particle[i] = Particle();
            m_particle[i].used      = true;
            m_particle[i].ray       = false;
            m_particle[i].uniqueStamp = m_uniqueStamp++;
            m_particle[i].sheet     = sheet;
            m_particle[i].mass      = mass;
            m_particle[i].weight    = weight;
            m_particle[i].duration  = duration;
            m_particle[i].pos       = pos;
            m_particle[i].goal      = pos;
            m_particle[i].speed     = speed;
            m_particle[i].windSensitivity = windSensitivity;
            m_particle[i].zoom      = 1.0f;
            m_particle[i].angle     = 0.0f;
            m_particle[i].intensity = 1.0f;
            m_particle[i].type      = type;
            m_particle[i].phase     = PARPHSTART;
            m_particle[i].texSup.x  = 0.0f;
            m_particle[i].texSup.y  = 0.0f;
            m_particle[i].texInf.x  = 0.0f;
            m_particle[i].texInf.y  = 0.0f;
            m_particle[i].time      = 0.0f;
            m_particle[i].phaseTime = 0.0f;
            m_particle[i].testTime  = 0.0f;
            m_particle[i].trackRank = -1;

            m_totalInterface[t][sheet] ++;

            return i | ((m_particle[i].uniqueStamp&0xffff)<<16);
        }
    }

    return -1;
}

/** Returns the channel of the particle created or -1 on error */
int CParticle::CreateRay(Math::Vector pos, Math::Vector goal,
                          ParticleType type, Math::Point dim,
                          float duration, int sheet)
{
    int t = -1;
    if ( type == PARTIRAY1 ||
         type == PARTIRAY2 ||
         type == PARTIRAY3 )
    {
        t = 3;  // effect02
    }
    if (t >= MAXPARTITYPE) return -1;
    if (t == -1) return -1;

    for (int j = 0; j < MAXPARTICULE; j++)
    {
        int i = MAXPARTICULE*t+j;

        if (!m_particle[i].used)
        {
            m_particle[i] = Particle();
            m_particle[i].used      = true;
            m_particle[i].ray       = true;
            m_particle[i].uniqueStamp = m_uniqueStamp++;
            m_particle[i].sheet     = sheet;
            m_particle[i].mass      = 0.0f;
            m_particle[i].duration  = duration;
            m_particle[i].pos       = pos;
            m_particle[i].goal      = goal;
            m_particle[i].speed     = Math::Vector(0.0f, 0.0f, 0.0f);
            m_particle[i].windSensitivity = 0.0f;
            m_particle[i].dim       = dim;
            m_particle[i].zoom      = 1.0f;
            m_particle[i].angle     = 0.0f;
            m_particle[i].intensity = 1.0f;
            m_particle[i].type      = type;
            m_particle[i].phase     = PARPHSTART;
            m_particle[i].texSup.x  = 0.0f;
            m_particle[i].texSup.y  = 0.0f;
            m_particle[i].texInf.x  = 0.0f;
            m_particle[i].texInf.y  = 0.0f;
            m_particle[i].time      = 0.0f;
            m_particle[i].phaseTime = 0.0f;
            m_particle[i].testTime  = 0.0f;
            m_particle[i].objLink   = nullptr;
            m_particle[i].objFather = nullptr;
            m_particle[i].trackRank = -1;

            m_totalInterface[t][sheet] ++;

            return i | ((m_particle[i].uniqueStamp&0xffff)<<16);
        }
    }

    return -1;
}

/** "length" is the length of the tail of drag (in seconds)! */
int CParticle::CreateTrack(Math::Vector pos, Math::Vector speed, Math::Point dim,
                           ParticleType type, float duration, float mass,
                           float length, float width)
{
    // Creates the normal particle.
    int channel = CreateParticle(pos, speed, dim, type, duration, mass, 0.0f, 0);
    if (channel == -1) return -1;

    // Seeks a streak free.
    for (int i = 0; i < MAXTRACK; i++)
    {
        if (!m_track[i].used)  // free?
        {
            int rank = channel;
            if (!CheckChannel(rank)) return -1;
            m_particle[rank].trackRank = i;

            m_track[i].used = true;
            m_track[i].step = (length/duration) / MAXTRACKLEN;
            m_track[i].last = 0.0f;
            m_track[i].intensity = 1.0f;
            m_track[i].width = width;
            m_track[i].posUsed = 1;
            m_track[i].head = 0;
            m_track[i].pos[0] = pos;
            break;
        }
    }

    return channel;
}

void CParticle::CreateWheelTrace(const Math::Vector &p1, const Math::Vector &p2,
                                 const Math::Vector &p3, const Math::Vector &p4,
                                 TraceColor color)
{
    int max = MAXWHEELTRACE;
    int i = m_wheelTraceIndex++;
    if (m_wheelTraceIndex > max)  m_wheelTraceIndex = 0;

    m_wheelTrace[i].color = color;
    m_wheelTrace[i].pos[0] = p1;  // ul
    m_wheelTrace[i].pos[1] = p2;  // dl
    m_wheelTrace[i].pos[2] = p3;  // ur
    m_wheelTrace[i].pos[3] = p4;  // dr

    if (m_terrain == nullptr)
        m_terrain = m_main->GetTerrain();

    m_terrain->AdjustToFloor(m_wheelTrace[i].pos[0]);
    m_wheelTrace[i].pos[0].y += 0.2f;  // just above the ground

    m_terrain->AdjustToFloor(m_wheelTrace[i].pos[1]);
    m_wheelTrace[i].pos[1].y += 0.2f;  // just above the ground

    m_terrain->AdjustToFloor(m_wheelTrace[i].pos[2]);
    m_wheelTrace[i].pos[2].y += 0.2f;  // just above the ground

    m_terrain->AdjustToFloor(m_wheelTrace[i].pos[3]);
    m_wheelTrace[i].pos[3].y += 0.2f;  // just above the ground

    if (m_wheelTraceTotal < max)
        m_wheelTraceTotal++;
    else
        m_wheelTraceTotal = max;
}



/** Adapts the channel so it can be used as an offset in m_particle */
bool CParticle::CheckChannel(int &channel)
{
    int uniqueStamp = (channel>>16)&0xffff;
    channel &= 0xffff;

    if (channel < 0)  return false;
    if (channel >= MAXPARTICULE*MAXPARTITYPE) return false;

    if (!m_particle[channel].used)
    {
        GetLogger()->Trace("Particle %d:%d doesn't exist anymore (used=false)\n", channel, uniqueStamp);
        return false;
    }

    if (m_particle[channel].uniqueStamp != uniqueStamp)
    {
        GetLogger()->Trace("Particle %d:%d doesn't exist anymore (uniqueStamp changed)\n", channel, uniqueStamp);
        return false;
    }

    return true;
}

void CParticle::DeleteRank(int rank)
{
    if (m_totalInterface[rank/MAXPARTICULE][m_particle[rank].sheet] > 0)
        m_totalInterface[rank/MAXPARTICULE][m_particle[rank].sheet]--;

    int i = m_particle[rank].trackRank;
    if (i != -1)  // drag associated?
        m_track[i].used = false;  // frees the drag

    m_particle[rank].used = false;
}

void CParticle::DeleteParticle(ParticleType type)
{
    for (int i = 0; i < MAXPARTICULE*MAXPARTITYPE; i++)
    {
        if (!m_particle[i].used) continue;
        if (m_particle[i].type != type) continue;

        DeleteRank(i);
    }
}

void CParticle::DeleteParticle(int channel)
{
    if (!CheckChannel(channel)) return;

    if (m_totalInterface[channel/MAXPARTICULE][m_particle[channel].sheet] > 0 )
        m_totalInterface[channel/MAXPARTICULE][m_particle[channel].sheet]--;

    int i = m_particle[channel].trackRank;
    if (i != -1)  // drag associated?
        m_track[i].used = false;  // frees the drag

    m_particle[channel].used = false;
}

void CParticle::SetObjectLink(int channel, CObject *object)
{
    if (!CheckChannel(channel))  return;
    m_particle[channel].objLink = object;
}

void CParticle::SetObjectFather(int channel, CObject *object)
{
    if (!CheckChannel(channel))  return;
    m_particle[channel].objFather = object;
}

void CParticle::SetPosition(int channel, Math::Vector pos)
{
    if (!CheckChannel(channel))  return;
    m_particle[channel].pos = pos;
}

void CParticle::SetDimension(int channel, Math::Point dim)
{
    if (!CheckChannel(channel))  return;
    m_particle[channel].dim = dim;
}

void CParticle::SetZoom(int channel, float zoom)
{
    if (!CheckChannel(channel))  return;
    m_particle[channel].zoom = zoom;
}

void CParticle::SetAngle(int channel, float angle)
{
    if (!CheckChannel(channel))  return;
    m_particle[channel].angle = angle;
}

void CParticle::SetIntensity(int channel, float intensity)
{
    if (!CheckChannel(channel))  return;
    m_particle[channel].intensity = intensity;
}

void CParticle::SetParam(int channel, Math::Vector pos, Math::Point dim, float zoom,
                          float angle, float intensity)
{
    if (!CheckChannel(channel))  return;
    m_particle[channel].pos       = pos;
    m_particle[channel].dim       = dim;
    m_particle[channel].zoom      = zoom;
    m_particle[channel].angle     = angle;
    m_particle[channel].intensity = intensity;
}

void CParticle::SetPhase(int channel, ParticlePhase phase, float duration)
{
    if (!CheckChannel(channel))  return;
    m_particle[channel].phase = phase;
    m_particle[channel].duration = duration;
    m_particle[channel].phaseTime = m_particle[channel].time;
}

bool CParticle::GetPosition(int channel, Math::Vector &pos)
{
    if (!CheckChannel(channel))  return false;
    pos = m_particle[channel].pos;
    return true;
}

void CParticle::SetFrameUpdate(int sheet, bool update)
{
    m_frameUpdate[sheet] = update;
}

void CParticle::FrameParticle(float rTime)
{
    if (m_main == nullptr)
        m_main = CRobotMain::GetInstancePointer();

    bool pause = (m_engine->GetPause() && !m_main->GetInfoLock());

    if (m_terrain == nullptr)
        m_terrain = m_main->GetTerrain();

    if (m_water == nullptr)
        m_water = m_engine->GetWater();

    if (!pause)
    {
        m_lastTimeGunDel += rTime;
        m_absTime += rTime;
    }

    Math::Vector wind = m_terrain->GetWind();
    Math::Vector eye = m_engine->GetEyePt();

    Math::Point ts, ti;
    Math::Vector pos;

    for (int i = 0; i < MAXPARTICULE*MAXPARTITYPE; i++)
    {
        if (!m_particle[i].used) continue;
        if (!m_frameUpdate[m_particle[i].sheet]) continue;

        if (m_particle[i].type != PARTISHOW)
        {
            if (pause && m_particle[i].sheet != SH_INTERFACE) continue;
        }

        if (m_particle[i].type != PARTIQUARTZ)
            m_particle[i].pos += m_particle[i].speed*rTime;

        if (m_particle[i].sheet == SH_WORLD)
        {
            float h = rTime*m_particle[i].windSensitivity*Math::Rand()*2.0f;
            m_particle[i].pos += wind*h;
        }

        float progress = (m_particle[i].time-m_particle[i].phaseTime)/m_particle[i].duration;

        // Manages the particles with mass that bounce.
        if ( m_particle[i].mass != 0.0f        &&
             m_particle[i].type != PARTIQUARTZ )
        {
            m_particle[i].speed.y -= m_particle[i].mass*rTime;

            float h;
            if (m_particle[i].sheet == SH_INTERFACE)
                h = 0.0f;
            else
                h = m_terrain->GetFloorLevel(m_particle[i].pos, true);

            h += m_particle[i].dim.y*0.75f;
            if (m_particle[i].pos.y < h)  // impact with the ground?
            {
                if ( m_particle[i].type == PARTIPART &&
                     m_particle[i].weight > 3.0f &&  // heavy enough?
                     m_particle[i].bounce < 3 )
                {
                    float amplitude = m_particle[i].weight*0.1f;
                    amplitude *= 1.0f-0.3f*m_particle[i].bounce;
                    if (amplitude > 1.0f)  amplitude = 1.0f;
                    if (amplitude > 0.0f)
                    {
                        Play(SOUND_BOUM, m_particle[i].pos, amplitude);
                    }
                }

                if (m_particle[i].bounce < 3)
                {
                    m_particle[i].pos.y = h;
                    m_particle[i].speed.y *= -0.4f;
                    m_particle[i].speed.x *=  0.4f;
                    m_particle[i].speed.z *=  0.4f;
                    m_particle[i].bounce ++;  // more impact
                }
                else    // disappears after 3 bounces?
                {
                    if ( m_particle[i].pos.y < h-10.0f ||
                         m_particle[i].time >= 20.0f   )
                    {
                        DeleteRank(i);
                        continue;
                    }
                }
            }
        }

        // Manages drag associated.
        int r = m_particle[i].trackRank;
        if (r != -1)  // drag exists?
        {
            if (TrackMove(r, m_particle[i].pos, progress))
            {
                DeleteRank(i);
                continue;
            }

            m_track[r].drawParticle = (progress < 1.0f);
        }

        if (m_particle[i].type == PARTITRACK1)  // technical explosion?
        {
            m_particle[i].zoom = 1.0f-(m_particle[i].time-m_particle[i].duration);

            ts.x = 0.375f;
            ts.y = 0.000f;
            ti.x = ts.x+0.125f;
            ti.y = ts.y+0.125f;
        }

        if (m_particle[i].type == PARTITRACK2)  // spray blue?
        {
            m_particle[i].zoom = 1.0f-(m_particle[i].time-m_particle[i].duration);

            ts.x = 0.500f;
            ts.y = 0.000f;
            ti.x = ts.x+0.125f;
            ti.y = ts.y+0.125f;
        }

        if (m_particle[i].type == PARTITRACK3)  // spider?
        {
            m_particle[i].zoom = 1.0f-(m_particle[i].time-m_particle[i].duration);

            ts.x = 0.500f;
            ts.y = 0.750f;
            ti.x = ts.x+0.125f;
            ti.y = ts.y+0.125f;
        }

        if (m_particle[i].type == PARTITRACK4)  // insect explosion?
        {
            m_particle[i].zoom = 1.0f-(m_particle[i].time-m_particle[i].duration);

            ts.x = 0.625f;
            ts.y = 0.000f;
            ti.x = ts.x+0.125f;
            ti.y = ts.y+0.125f;
        }

        if (m_particle[i].type == PARTITRACK5)  // derrick?
        {
            m_particle[i].zoom = 1.0f-(m_particle[i].time-m_particle[i].duration);

            ts.x = 0.750f;
            ts.y = 0.000f;
            ti.x = ts.x+0.125f;
            ti.y = ts.y+0.125f;
        }

        if (m_particle[i].type == PARTITRACK6)  // reset in/out?
        {
            ts.x = 0.0f;
            ts.y = 0.0f;
            ti.x = 0.0f;
            ti.y = 0.0f;
        }

        if ( m_particle[i].type == PARTITRACK7  ||  // win-1 ?
             m_particle[i].type == PARTITRACK8  ||  // win-2 ?
             m_particle[i].type == PARTITRACK9  ||  // win-3 ?
             m_particle[i].type == PARTITRACK10 )   // win-4 ?
        {
            m_particle[i].zoom = 1.0f-(m_particle[i].time-m_particle[i].duration);

            ts.x = 0.25f*(m_particle[i].type-PARTITRACK7);
            ts.y = 0.25f;
            ti.x = ts.x+0.25f;
            ti.y = ts.y+0.25f;
        }

        if (m_particle[i].type == PARTITRACK11)  // phazer shot?
        {
            CObject* object = SearchObjectGun(m_particle[i].goal, m_particle[i].pos, m_particle[i].type, m_particle[i].objFather);
            m_particle[i].goal = m_particle[i].pos;
            if (object != nullptr && object->Implements(ObjectInterfaceType::Damageable))
            {
                dynamic_cast<CDamageableObject&>(*object).DamageObject(DamageType::Phazer, 0.002f, m_particle[i].objFather);
            }

            m_particle[i].zoom = 1.0f-(m_particle[i].time-m_particle[i].duration);

            ts.x = 0.375f;
            ts.y = 0.000f;
            ti.x = ts.x+0.125f;
            ti.y = ts.y+0.125f;
        }

        if (m_particle[i].type == PARTITRACK12)  // drag reactor?
        {
            m_particle[i].zoom = 1.0f;

            ts.x = 0.375f;
            ts.y = 0.000f;
            ti.x = ts.x+0.125f;
            ti.y = ts.y+0.125f;
        }

        if (m_particle[i].type == PARTIMOTOR)
        {
            if (progress >= 1.0f)
            {
                DeleteRank(i);
                continue;
            }

            m_particle[i].zoom = 1.0f-progress;
            m_particle[i].intensity = 1.0f-progress;

            ts.x = 0.000f;
            ts.y = 0.750f;
            ti.x = ts.x+0.125f;
            ti.y = ts.y+0.125f;
        }

        if (m_particle[i].type == PARTIBLITZ)
        {
            if (progress >= 1.0f)
            {
                DeleteRank(i);
                continue;
            }

            m_particle[i].zoom = 1.0f-progress;
            m_particle[i].angle = Math::Rand()*Math::PI*2.0f;

            ts.x = 0.125f;
            ts.y = 0.750f;
            ti.x = ts.x+0.125f;
            ti.y = ts.y+0.125f;
        }

        if (m_particle[i].type == PARTICRASH)
        {
            if (progress >= 1.0f)
            {
                DeleteRank(i);
                continue;
            }

            if (progress < 0.25f)
                m_particle[i].zoom = progress/0.25f;
            else
                m_particle[i].intensity = 1.0f-(progress-0.25f)/0.75f;

            ts.x = 0.000f;
            ts.y = 0.750f;
            ti.x = ts.x+0.125f;
            ti.y = ts.y+0.125f;
        }

        if (m_particle[i].type == PARTIVAPOR)
        {
            if (progress >= 1.0f)
            {
                DeleteRank(i);
                continue;
            }

            m_particle[i].intensity = 1.0f-progress;
            m_particle[i].zoom = 1.0f+progress*3.0f;

            ts.x = 0.000f;
            ts.y = 0.750f;
            ti.x = ts.x+0.125f;
            ti.y = ts.y+0.125f;
        }

        if (m_particle[i].type == PARTIGAS)
        {
            if (progress >= 1.0f)
            {
                DeleteRank(i);
                continue;
            }

            m_particle[i].zoom = 1.0f-progress;

            ts.x = 0.375f;
            ts.y = 0.750f;
            ti.x = ts.x+0.125f;
            ti.y = ts.y+0.125f;
        }

        if (m_particle[i].type == PARTIBASE)
        {
            if (progress >= 1.0f)
            {
                DeleteRank(i);
                continue;
            }

            m_particle[i].zoom = 1.0f+progress*7.0f;
            m_particle[i].intensity = powf(1.0f-progress, 3.0f);

            ts.x = 0.375f;
            ts.y = 0.750f;
            ti.x = ts.x+0.125f;
            ti.y = ts.y+0.125f;
        }

        if ( m_particle[i].type == PARTIFIRE  ||
             m_particle[i].type == PARTIFIREZ )
        {
            if (progress >= 1.0f)
            {
                DeleteRank(i);
                continue;
            }

            if (m_particle[i].type == PARTIFIRE)
                m_particle[i].zoom = 1.0f-progress;
            else
                m_particle[i].zoom = progress;

            ts.x = 0.500f;
            ts.y = 0.750f;
            ti.x = ts.x+0.125f;
            ti.y = ts.y+0.125f;
        }

        if (m_particle[i].type == PARTIGUN1)  // fireball shot?
        {
            if (progress >= 1.0f)
            {
                DeleteRank(i);
                continue;
            }

            if (m_particle[i].testTime >= 0.05f)
            {
                m_particle[i].testTime = 0.0f;

                if (m_terrain->GetHeightToFloor(m_particle[i].pos, true) < -2.0f)
                {
                    m_exploGunCounter++;

                    if (m_exploGunCounter % 2 == 0)
                    {
                        pos = m_particle[i].goal;
                        m_terrain->AdjustToFloor(pos, true);
                        Math::Vector speed;
                        speed.x = 0.0f;
                        speed.z = 0.0f;
                        speed.y = 0.0f;
                        Math::Point dim;
                        dim.x = Math::Rand()*6.0f+6.0f;
                        dim.y = dim.x;
                        float duration = Math::Rand()*1.0f+1.0f;
                        float mass = 0.0f;
                        CreateParticle(pos, speed, dim, PARTIEXPLOG1, duration, mass, 1.0f);

                        pos.y += 1.0f;
                        int total = static_cast<int>(2.0f*m_engine->GetParticleDensity());
                        for (int j = 0; j < total; j++)
                        {
                            speed.x = (Math::Rand()-0.5f)*20.0f;
                            speed.z = (Math::Rand()-0.5f)*20.0f;
                            speed.y = Math::Rand()*20.0f;
                            dim.x = 1.0f;
                            dim.y = dim.x;
                            duration = Math::Rand()*1.0f+1.0f;
                            mass = Math::Rand()*10.0f+15.0f;
                            CreateParticle(pos, speed, dim, PARTIEXPLOG1, duration, mass, 1.0f);
                        }
                    }

                    if (m_exploGunCounter % 4 == 0)
                        Play(SOUND_EXPLOg1, pos, 0.5f);

                    DeleteRank(i);
                    continue;
                }

                CObject* object = SearchObjectGun(m_particle[i].goal, m_particle[i].pos, m_particle[i].type, m_particle[i].objFather);
                m_particle[i].goal = m_particle[i].pos;
                if (object != nullptr)
                {
                    if (object->Implements(ObjectInterfaceType::Damageable))
                    {
                        dynamic_cast<CDamageableObject&>(*object).DamageObject(DamageType::Fire, 0.001f, m_particle[i].objFather);
                    }

                    m_exploGunCounter++;

                    if (m_exploGunCounter % 2 == 0)
                    {
                        pos = m_particle[i].pos;
                        Math::Vector speed;
                        speed.x = 0.0f;
                        speed.z = 0.0f;
                        speed.y = 0.0f;
                        Math::Point dim;
                        dim.x = Math::Rand()*6.0f+6.0f;
                        dim.y = dim.x;
                        float duration = Math::Rand()*1.0f+1.0f;
                        float mass = 0.0f;
                        CreateParticle(pos, speed, dim, PARTIEXPLOG1, duration, mass, 1.0f);

                        pos.y += 1.0f;
                        int total = static_cast<int>(2.0f*m_engine->GetParticleDensity());
                        for (int j = 0; j < total; j++)
                        {
                            speed.x = (Math::Rand()-0.5f)*20.0f;
                            speed.z = (Math::Rand()-0.5f)*20.0f;
                            speed.y = Math::Rand()*20.0f;
                            dim.x = 1.0f;
                            dim.y = dim.x;
                            duration = Math::Rand()*1.0f+1.0f;
                            mass = Math::Rand()*10.0f+15.0f;
                            CreateParticle(pos, speed, dim, PARTIEXPLOG1, duration, mass, 1.0f);
                        }
                    }

                    if (m_exploGunCounter % 4 == 0)
                        Play(SOUND_EXPLOg1, pos, 0.5f);

                    DeleteRank(i);
                    continue;
                }
            }

            m_particle[i].angle -= rTime*Math::PI*8.0f;
            m_particle[i].zoom = 1.0f-progress;

            ts.x = 0.00f;
            ts.y = 0.50f;
            ti.x = ts.x+0.25f;
            ti.y = ts.y+0.25f;
        }

        if (m_particle[i].type == PARTIGUN2)  // ant shot?
        {
            if (progress >= 1.0f)
            {
                DeleteRank(i);
                continue;
            }

            if (m_particle[i].testTime >= 0.1f)
            {
                m_particle[i].testTime = 0.0f;
                CObject* object = SearchObjectGun(m_particle[i].goal, m_particle[i].pos, m_particle[i].type, m_particle[i].objFather);
                m_particle[i].goal = m_particle[i].pos;
                if (object != nullptr)
                {
                    if (object->GetType() == OBJECT_MOBILErs && dynamic_cast<CShielder&>(*object).GetActiveShieldRadius() > 0.0f)  // protected by shield?
                    {
                        CreateParticle(m_particle[i].pos, Math::Vector(0.0f, 0.0f, 0.0f), Math::Point(6.0f, 6.0f), PARTIGUNDEL, 2.0f);
                        if (m_lastTimeGunDel > 0.2f)
                        {
                            m_lastTimeGunDel = 0.0f;
                            Play(SOUND_GUNDEL, m_particle[i].pos, 1.0f);
                        }
                        DeleteRank(i);
                        continue;
                    }
                    else
                    {
                        if (object->GetType() != OBJECT_HUMAN)
                            Play(SOUND_TOUCH, m_particle[i].pos, 1.0f);

                        if (object->Implements(ObjectInterfaceType::Damageable))
                        {
                            dynamic_cast<CDamageableObject&>(*object).DamageObject(DamageType::Organic, 0.1f, m_particle[i].objFather);  // starts explosion
                        }
                    }
                }
            }

            m_particle[i].angle = Math::Rand()*Math::PI*2.0f;
            m_particle[i].zoom = 1.0f-progress;

            ts.x = 0.125f;
            ts.y = 0.875f;
            ti.x = ts.x+0.125f;
            ti.y = ts.y+0.125f;
        }

        if (m_particle[i].type == PARTIGUN3)  // spider suicides?
        {
            if (progress >= 1.0f)
            {
                DeleteRank(i);
                continue;
            }

            if (m_particle[i].testTime >= 0.1f)
            {
                m_particle[i].testTime = 0.0f;
                CObject* object = SearchObjectGun(m_particle[i].goal, m_particle[i].pos, m_particle[i].type, m_particle[i].objFather);
                m_particle[i].goal = m_particle[i].pos;
                if (object != nullptr)
                {
                    if (object->GetType() == OBJECT_MOBILErs && dynamic_cast<CShielder&>(*object).GetActiveShieldRadius() > 0.0f)
                    {
                        CreateParticle(m_particle[i].pos, Math::Vector(0.0f, 0.0f, 0.0f), Math::Point(6.0f, 6.0f), PARTIGUNDEL, 2.0f);
                        if (m_lastTimeGunDel > 0.2f)
                        {
                            m_lastTimeGunDel = 0.0f;
                            Play(SOUND_GUNDEL, m_particle[i].pos, 1.0f);
                        }
                        DeleteRank(i);
                        continue;
                    }
                    else
                    {
                        if (object->Implements(ObjectInterfaceType::Damageable))
                        {
                            dynamic_cast<CDamageableObject&>(*object).DamageObject(DamageType::Fire, std::numeric_limits<float>::infinity(), m_particle[i].objFather);  // starts explosion
                        }
                    }
                }
            }

            ts.x = 0.500f;
            ts.y = 0.750f;
            ti.x = ts.x+0.125f;
            ti.y = ts.y+0.125f;
        }

        if (m_particle[i].type == PARTIGUN4)  // orgaball shot?
        {
            if (progress >= 1.0f)
            {
                DeleteRank(i);
                continue;
            }

            if (m_particle[i].testTime >= 0.05f)
            {
                m_particle[i].testTime = 0.0f;

                if (m_terrain->GetHeightToFloor(m_particle[i].pos, true) < -2.0f)
                {
                    m_exploGunCounter ++;

                    if (m_exploGunCounter % 2 == 0)
                    {
                        pos = m_particle[i].goal;
                        m_terrain->AdjustToFloor(pos, true);
                        Math::Vector speed;
                        speed.x = 0.0f;
                        speed.z = 0.0f;
                        speed.y = 0.0f;
                        Math::Point dim;
                        dim.x = Math::Rand()*4.0f+2.0f;
                        dim.y = dim.x;
                        float duration = Math::Rand()*0.7f+0.7f;
                        float mass = 0.0f;
                        CreateParticle(pos, speed, dim, PARTIEXPLOG2, duration, mass, 1.0f);
                    }

                    if (m_exploGunCounter % 4 == 0)
                    {
                        Play(SOUND_EXPLOg2, pos, 0.5f);
                    }

                    DeleteRank(i);
                    continue;
                }

                CObject* object = SearchObjectGun(m_particle[i].goal, m_particle[i].pos, m_particle[i].type, m_particle[i].objFather);
                m_particle[i].goal = m_particle[i].pos;
                if (object != nullptr)
                {
                    if (object->Implements(ObjectInterfaceType::Damageable))
                    {
                        dynamic_cast<CDamageableObject&>(*object).DamageObject(DamageType::Organic, 0.001f, m_particle[i].objFather);
                    }

                    m_exploGunCounter ++;

                    if (m_exploGunCounter % 2 == 0)
                    {
                        pos = m_particle[i].pos;
                        Math::Vector speed;
                        speed.x = 0.0f;
                        speed.z = 0.0f;
                        speed.y = 0.0f;
                        Math::Point dim;
                        dim.x = Math::Rand()*4.0f+2.0f;
                        dim.y = dim.x;
                        float duration = Math::Rand()*0.7f+0.7f;
                        float mass = 0.0f;
                        CreateParticle(pos, speed, dim, PARTIEXPLOG2, duration, mass, 1.0f);
                    }

                    if (m_exploGunCounter % 4 == 0)
                        Play(SOUND_EXPLOg2, pos, 0.5f);

                    DeleteRank(i);
                    continue;
                }
            }

            m_particle[i].angle = Math::Rand()*Math::PI*2.0f;
            m_particle[i].zoom = 1.0f-progress;

            ts.x = 0.125f;
            ts.y = 0.875f;
            ti.x = ts.x+0.125f;
            ti.y = ts.y+0.125f;
        }

        if (m_particle[i].type == PARTIFLIC)
        {
            if (progress >= 1.0f)
            {
                DeleteRank(i);
                continue;
            }

            m_particle[i].zoom = 0.1f+progress;
            m_particle[i].intensity = 1.0f-progress;

            ts.x = 0.00f;
            ts.y = 0.75f;
            ti.x = ts.x+0.25f;
            ti.y = ts.y+0.25f;
        }

        if (m_particle[i].type == PARTISHOW)
        {
            if (progress >= 1.0f)
            {
                DeleteRank(i);
                continue;
            }

            if (progress < 0.5f) m_particle[i].intensity = progress/0.5f;
            else                 m_particle[i].intensity = 2.0f-progress/0.5f;
            m_particle[i].zoom = 1.0f-progress*0.8f;
            m_particle[i].angle -= rTime*Math::PI*0.5f;

            ts.x = 0.50f;
            ts.y = 0.00f;
            ti.x = ts.x+0.25f;
            ti.y = ts.y+0.25f;
        }

        if (m_particle[i].type == PARTICHOC)
        {
            if (progress >= 1.0f)
            {
                DeleteRank(i);
                continue;
            }

            m_particle[i].zoom = 0.1f+progress;
            m_particle[i].intensity = 1.0f-progress;

            ts.x = 0.50f;
            ts.y = 0.50f;
            ti.x = ts.x+0.25f;
            ti.y = ts.y+0.25f;
        }

        if (m_particle[i].type == PARTIGFLAT)
        {
            if (progress >= 1.0f)
            {
                DeleteRank(i);
                continue;
            }

            m_particle[i].zoom = 0.1f+progress;
            m_particle[i].intensity = 1.0f-progress;
            m_particle[i].angle -= rTime*Math::PI*2.0f;

            ts.x = 0.00f;
            ts.y = 0.50f;
            ti.x = ts.x+0.25f;
            ti.y = ts.y+0.25f;
        }

        if (m_particle[i].type == PARTILIMIT1)
        {
            if (progress >= 1.0f)
            {
                DeleteRank(i);
                continue;
            }

            m_particle[i].zoom = 1.0f;
            m_particle[i].intensity = 1.0f;

            ts.x = 0.000f;
            ts.y = 0.125f;
            ti.x = ts.x+0.125f;
            ti.y = ts.y+0.125f;
        }
        if (m_particle[i].type == PARTILIMIT2)
        {
            if (progress >= 1.0f)
            {
                DeleteRank(i);
                continue;
            }

            m_particle[i].zoom = 1.0f;
            m_particle[i].intensity = 1.0f;

            ts.x = 0.375f;
            ts.y = 0.125f;
            ti.x = ts.x+0.125f;
            ti.y = ts.y+0.125f;
        }
        if (m_particle[i].type == PARTILIMIT3)
        {
            if (progress >= 1.0f)
            {
                DeleteRank(i);
                continue;
            }

            m_particle[i].zoom = 1.0f;
            m_particle[i].intensity = 1.0f;

            ts.x = 0.500f;
            ts.y = 0.125f;
            ti.x = ts.x+0.125f;
            ti.y = ts.y+0.125f;
        }

        if (m_particle[i].type == PARTIFOG0)
        {
            m_particle[i].zoom = progress;
            m_particle[i].intensity = 0.3f+sinf(progress)*0.15f;
            m_particle[i].angle += rTime*0.05f;

            ts.x = 0.25f;
            ts.y = 0.75f;
            ti.x = ts.x+0.25f;
            ti.y = ts.y+0.25f;
        }
        if (m_particle[i].type == PARTIFOG1)
        {
            m_particle[i].zoom = progress;
            m_particle[i].intensity = 0.3f+sinf(progress)*0.15f;
            m_particle[i].angle -= rTime*0.07f;

            ts.x = 0.25f;
            ts.y = 0.75f;
            ti.x = ts.x+0.25f;
            ti.y = ts.y+0.25f;
        }

        if (m_particle[i].type == PARTIFOG2)
        {
            m_particle[i].zoom = progress;
            m_particle[i].intensity = 0.6f+sinf(progress)*0.15f;
            m_particle[i].angle += rTime*0.05f;

            ts.x = 0.75f;
            ts.y = 0.75f;
            ti.x = ts.x+0.25f;
            ti.y = ts.y+0.25f;
        }
        if (m_particle[i].type == PARTIFOG3)
        {
            m_particle[i].zoom = progress;
            m_particle[i].intensity = 0.6f+sinf(progress)*0.15f;
            m_particle[i].angle -= rTime*0.07f;

            ts.x = 0.75f;
            ts.y = 0.75f;
            ti.x = ts.x+0.25f;
            ti.y = ts.y+0.25f;
        }

        if (m_particle[i].type == PARTIFOG4)
        {
            m_particle[i].zoom = progress;
            m_particle[i].intensity = 0.5f+sinf(progress)*0.2f;
            m_particle[i].angle += rTime*0.05f;

            ts.x = 0.00f;
            ts.y = 0.25f;
            ti.x = ts.x+0.25f;
            ti.y = ts.y+0.25f;
        }
        if (m_particle[i].type == PARTIFOG5)
        {
            m_particle[i].zoom = progress;
            m_particle[i].intensity = 0.5f+sinf(progress)*0.2f;
            m_particle[i].angle -= rTime*0.07f;

            ts.x = 0.00f;
            ts.y = 0.25f;
            ti.x = ts.x+0.25f;
            ti.y = ts.y+0.25f;
        }

        if (m_particle[i].type == PARTIFOG6)
        {
            m_particle[i].zoom = progress;
            m_particle[i].intensity = 0.5f+sinf(progress)*0.2f;
            m_particle[i].angle += rTime*0.05f;

            ts.x = 0.50f;
            ts.y = 0.25f;
            ti.x = ts.x+0.25f;
            ti.y = ts.y+0.25f;
        }
        if (m_particle[i].type == PARTIFOG7)
        {
            m_particle[i].zoom = progress;
            m_particle[i].intensity = 0.5f+sinf(progress)*0.2f;
            m_particle[i].angle -= rTime*0.07f;

            ts.x = 0.50f;
            ts.y = 0.25f;
            ti.x = ts.x+0.25f;
            ti.y = ts.y+0.25f;
        }

        // Decreases the intensity if the camera
        // is almost at the same height (fog was eye level).
        if ( m_particle[i].type >= PARTIFOG0 &&
             m_particle[i].type <= PARTIFOG7 )
        {
            float h = 10.0f;

            if ( m_particle[i].pos.y >= eye.y   &&
                 m_particle[i].pos.y <  eye.y+h )
            {
                m_particle[i].intensity *= (m_particle[i].pos.y-eye.y)/h;
            }
            if ( m_particle[i].pos.y >  eye.y-h &&
                 m_particle[i].pos.y <  eye.y   )
            {
                m_particle[i].intensity *= (eye.y-m_particle[i].pos.y)/h;
            }
        }

        if ( m_particle[i].type == PARTIEXPLOT ||
             m_particle[i].type == PARTIEXPLOO )
        {
            if (progress >= 1.0f)
            {
                DeleteRank(i);
                continue;
            }

            m_particle[i].zoom = 1.0f-progress/2.0f;
            m_particle[i].intensity = 1.0f-progress;

            if (m_particle[i].type == PARTIEXPLOT)  ts.x = 0.750f;
            else                                    ts.x = 0.875f;
            ts.y = 0.750f;
            ti.x = ts.x+0.125f;
            ti.y = ts.y+0.125f;
        }

        if (m_particle[i].type == PARTIEXPLOG1)
        {
            if (progress >= 1.0f)
            {
                DeleteRank(i);
                continue;
            }

            m_particle[i].intensity = 1.0f-progress;

            ts.x = 0.375f;
            ts.y = 0.000f;
            ti.x = ts.x+0.125f;
            ti.y = ts.y+0.125f;
        }
        if (m_particle[i].type == PARTIEXPLOG2)
        {
            if (progress >= 1.0f)
            {
                DeleteRank(i);
                continue;
            }

            m_particle[i].intensity = 1.0f-progress;

            ts.x = 0.625f;
            ts.y = 0.000f;
            ti.x = ts.x+0.125f;
            ti.y = ts.y+0.125f;
        }

        if (m_particle[i].type == PARTIFLAME)
        {
            if (progress >= 1.0f)
            {
                DeleteRank(i);
                continue;
            }

            m_particle[i].zoom = 1.0f-progress/2.0f;
            if (progress < 0.5f)
            {
                m_particle[i].intensity = progress/0.5f;
            }
            else
            {
                m_particle[i].intensity = 2.0f-progress/0.5f;
            }

            ts.x = 0.750f;
            ts.y = 0.750f;
            ti.x = ts.x+0.125f;
            ti.y = ts.y+0.125f;
        }

        if (m_particle[i].type == PARTIBUBBLE)
        {
            if ( progress >= 1.0f ||
                 m_particle[i].pos.y >= m_water->GetLevel() )
            {
                DeleteRank(i);
                continue;
            }

            m_particle[i].zoom = 1.0f-progress/2.0f;
            m_particle[i].intensity = 1.0f-progress;

            ts.x = 0.250f;
            ts.y = 0.875f;
            ti.x = ts.x+0.125f;
            ti.y = ts.y+0.125f;
        }

        if ( m_particle[i].type == PARTISMOKE1 ||
             m_particle[i].type == PARTISMOKE2 ||
             m_particle[i].type == PARTISMOKE3 )
        {
            if (progress >= 1.0f)
            {
                DeleteRank(i);
                continue;
            }

            if (progress < 0.25f)
            {
                m_particle[i].zoom = progress/0.25f;
            }
            else
            {
                m_particle[i].intensity = 1.0f-(progress-0.25f)/0.75f;
            }

            ts.x = 0.500f+0.125f*(m_particle[i].type-PARTISMOKE1);
            ts.y = 0.750f;
            ti.x = ts.x+0.125f;
            ti.y = ts.y+0.125f;
        }

        if (m_particle[i].type == PARTIBLOOD)
        {
            if (progress >= 1.0f)
            {
                DeleteRank(i);
                continue;
            }

            m_particle[i].intensity = 1.0f-progress;

            ts.x = 0.750f+(rand()%2)*0.125f;
            ts.y = 0.875f;
            ti.x = ts.x+0.125f;
            ti.y = ts.y+0.125f;
        }

        if (m_particle[i].type == PARTIBLOODM)
        {
            if (progress >= 1.0f)
            {
                DeleteRank(i);
                continue;
            }

            m_particle[i].intensity = 1.0f-progress;

            ts.x = 0.875f;
            ts.y = 0.750f;
            ti.x = ts.x+0.125f;
            ti.y = ts.y+0.125f;
        }

        if ( m_particle[i].type == PARTIVIRUS )
        {
            if (progress >= 1.0f)
            {
                DeleteRank(i);
                continue;
            }

            if (progress < 0.25f)
                m_particle[i].zoom = progress/0.25f;
            else
                m_particle[i].intensity = 1.0f-(progress-0.25f)/0.75f;

            m_particle[i].angle += rTime*Math::PI*1.0f;
        }

        if (m_particle[i].type == PARTIBLUE)
        {
            if (progress >= 1.0f)
            {
                DeleteRank(i);
                continue;
            }

            m_particle[i].zoom = 1.0f-progress;

            ts.x = 0.625f;
            ts.y = 0.750f;
            ti.x = ts.x+0.125f;
            ti.y = ts.y+0.125f;
        }

        if (m_particle[i].type == PARTIROOT)
        {
            if (progress >= 1.0f)
            {
                DeleteRank(i);
                continue;
            }

            if (progress < 0.25f)
            {
                m_particle[i].zoom = progress/0.25f;
            }
            else
            {
                m_particle[i].intensity = 1.0f-(progress-0.25f)/0.75f;
            }

            ts.x = 0.000f;
            ts.y = 0.000f;
            ti.x = ts.x+0.125f;
            ti.y = ts.y+0.125f;
        }

        if (m_particle[i].type == PARTIRECOVER)
        {
            if (progress >= 1.0f)
            {
                DeleteRank(i);
                continue;
            }

            if (progress < 0.25f)
            {
                m_particle[i].zoom = progress/0.25f;
            }
            else
            {
                m_particle[i].intensity = 1.0f-(progress-0.25f)/0.75f;
            }

            ts.x = 0.875f;
            ts.y = 0.000f;
            ti.x = ts.x+0.125f;
            ti.y = ts.y+0.125f;
        }

        if (m_particle[i].type == PARTIEJECT)
        {
            if (progress >= 1.0f)
            {
                DeleteRank(i);
                continue;
            }

            m_particle[i].zoom = 1.0f+powf(progress, 2.0f)*5.0f;
            m_particle[i].intensity = 1.0f-progress;

            ts.x = 0.625f;
            ts.y = 0.875f;
            ti.x = ts.x+0.125f;
            ti.y = ts.y+0.125f;
        }

        if (m_particle[i].type == PARTISCRAPS)
        {
            if (progress >= 1.0f)
            {
                DeleteRank(i);
                continue;
            }

            m_particle[i].zoom = 1.0f-progress;

            ts.x = 0.625f;
            ts.y = 0.875f;
            ti.x = ts.x+0.125f;
            ti.y = ts.y+0.125f;
        }

        if (m_particle[i].type == PARTIFRAG)
        {
            m_particle[i].angle += rTime*Math::PI*0.5f;

            ts.x = 0.0f;
            ts.y = 0.0f;
            ti.x = 0.0f;
            ti.y = 0.0f;
        }

        if (m_particle[i].type == PARTIPART)
        {
            ts.x = 0.0f;
            ts.y = 0.0f;
            ti.x = 0.0f;
            ti.y = 0.0f;
        }

        if (m_particle[i].type == PARTIQUEUE)
        {
            if (m_particle[i].testTime >= 0.05f)
            {
                m_particle[i].testTime = 0.0f;

                pos = m_particle[i].pos;
                Math::Vector speed = Math::Vector(0.0f, 0.0f, 0.0f);
                Math::Point dim;
                dim.x = 1.0f*(Math::Rand()*0.8f+0.6f);
                dim.y = dim.x;
                CreateParticle(pos, speed, dim, PARTIGAS, 0.5f);
            }

            ts.x = 0.375f;
            ts.y = 0.750f;
            ti.x = ts.x+0.125f;
            ti.y = ts.y+0.125f;
        }

        if (m_particle[i].type == PARTIORGANIC1)
        {
            if (progress >= 1.0f)
            {
                DeleteRank(i);

                pos = m_particle[i].pos;
                Math::Point dim;
                dim.x    = m_particle[i].dim.x/4.0f;
                dim.y    = dim.x;
                float duration = m_particle[i].duration;
                float mass     = m_particle[i].mass;
                int total = static_cast<int>((10.0f*m_engine->GetParticleDensity()));
                for (int j = 0; j < total; j++)
                {
                    Math::Vector speed;
                    speed.x = (Math::Rand()-0.5f)*20.0f;
                    speed.y = (Math::Rand()-0.5f)*20.0f;
                    speed.z = (Math::Rand()-0.5f)*20.0f;
                    CreateParticle(pos, speed, dim, PARTIORGANIC2, duration, mass);
                }
                total = static_cast<int>((5.0f*m_engine->GetParticleDensity()));
                for (int j = 0; j < total; j++)
                {
                    Math::Vector speed;
                    speed.x = (Math::Rand()-0.5f)*20.0f;
                    speed.y = (Math::Rand()-0.5f)*20.0f;
                    speed.z = (Math::Rand()-0.5f)*20.0f;
                    duration *= Math::Rand()+0.8f;
                    CreateTrack(pos, speed, dim, PARTITRACK4, duration, mass, duration*0.2f, dim.x*2.0f);
                }
                continue;
            }

            m_particle[i].zoom = (m_particle[i].time-m_particle[i].duration);

            ts.x = 0.125f;
            ts.y = 0.875f;
            ti.x = ts.x+0.125f;
            ti.y = ts.y+0.125f;
        }

        if (m_particle[i].type == PARTIORGANIC2)
        {
            if (progress >= 1.0f)
            {
                DeleteRank(i);
                continue;
            }

            m_particle[i].zoom = 1.0f-(m_particle[i].time-m_particle[i].duration);

            ts.x = 0.125f;
            ts.y = 0.875f;
            ti.x = ts.x+0.125f;
            ti.y = ts.y+0.125f;
        }

        if (m_particle[i].type == PARTIGLINT)
        {
            if (progress >= 1.0f)
            {
                DeleteRank(i);
                continue;
            }

            if (progress > 0.5f)
                m_particle[i].zoom = 1.0f-(progress-0.5f)*2.0f;

            m_particle[i].angle = m_particle[i].time*Math::PI;

            ts.x = 0.75f;
            ts.y = 0.25f;
            ti.x = ts.x+0.25f;
            ti.y = ts.y+0.25f;
        }

        if (m_particle[i].type == PARTIGLINTb)
        {
            if (progress >= 1.0f)
            {
                DeleteRank(i);
                continue;
            }

            if (progress > 0.5f)
                m_particle[i].zoom = 1.0f-(progress-0.5f)*2.0f;

            m_particle[i].angle = m_particle[i].time*Math::PI;

            ts.x = 0.75f;
            ts.y = 0.50f;
            ti.x = ts.x+0.25f;
            ti.y = ts.y+0.25f;
        }

        if (m_particle[i].type == PARTIGLINTr)
        {
            if (progress >= 1.0f)
            {
                DeleteRank(i);
                continue;
            }

            if (progress > 0.5f)
                m_particle[i].zoom = 1.0f-(progress-0.5f)*2.0f;

            m_particle[i].angle = m_particle[i].time*Math::PI;

            ts.x = 0.75f;
            ts.y = 0.00f;
            ti.x = ts.x+0.25f;
            ti.y = ts.y+0.25f;
        }

        if ( m_particle[i].type >= PARTILENS1 &&
             m_particle[i].type <= PARTILENS4 )
        {
            if (progress >= 1.0f)
            {
                DeleteRank(i);
                continue;
            }

            if (progress < 0.5f)
                m_particle[i].zoom = progress*2.0f;
            else
                m_particle[i].intensity = 1.0f-(progress-0.5f)*2.0f;

            ts.x = 0.25f*(m_particle[i].type-PARTILENS1);
            ts.y = 0.25f;
            ti.x = ts.x+0.25f;
            ti.y = ts.y+0.25f;
        }

        if (m_particle[i].type == PARTICONTROL)
        {
            if (progress >= 1.0f)
            {
                DeleteRank(i);
                continue;
            }

            if (progress < 0.3f)
            {
                m_particle[i].zoom = progress/0.3f;
            }
            else
            {
                m_particle[i].zoom = 1.0f;
                m_particle[i].intensity = 1.0f-(progress-0.3f)/0.7f;
            }

            ts.x = 0.00f;
            ts.y = 0.00f;
            ti.x = ts.x+0.25f;
            ti.y = ts.y+0.25f;
        }

        if (m_particle[i].type == PARTIGUNDEL)
        {
            if (progress >= 1.0f)
            {
                DeleteRank(i);
                continue;
            }

            if (progress > 0.5f)
                m_particle[i].zoom = 1.0f-(m_particle[i].time-m_particle[i].duration/2.0f);

            m_particle[i].angle = m_particle[i].time*Math::PI;

            ts.x = 0.75f;
            ts.y = 0.50f;
            ti.x = ts.x+0.25f;
            ti.y = ts.y+0.25f;
        }

        if (m_particle[i].type == PARTIQUARTZ)
        {
            if (progress >= 1.0f)
            {
                m_particle[i].time = 0.0f;
                m_particle[i].duration = 0.5f+Math::Rand()*2.0f;
                m_particle[i].pos.x = m_particle[i].speed.x + (Math::Rand()-0.5f)*m_particle[i].mass;
                m_particle[i].pos.y = m_particle[i].speed.y + (Math::Rand()-0.5f)*m_particle[i].mass;
                m_particle[i].pos.z = m_particle[i].speed.z + (Math::Rand()-0.5f)*m_particle[i].mass;
                m_particle[i].dim.x = 0.5f+Math::Rand()*1.5f;
                m_particle[i].dim.y = m_particle[i].dim.x;
                progress = 0.0f;
            }

            if (progress < 0.2f)
            {
                m_particle[i].zoom = progress/0.2f;
                m_particle[i].intensity = 1.0f;
            }
            else
            {
                m_particle[i].zoom = 1.0f;
                m_particle[i].intensity = 1.0f-(progress-0.2f)/0.8f;
            }

            ts.x = 0.25f;
            ts.y = 0.25f;
            ti.x = ts.x+0.25f;
            ti.y = ts.y+0.25f;
        }

        if (m_particle[i].type == PARTITOTO)
        {
            if (progress >= 1.0f)
            {
                DeleteRank(i);
                continue;
            }

            m_particle[i].zoom = 1.0f-progress;
            if (progress < 0.15f)
                m_particle[i].intensity = progress/0.15f;
            else
                m_particle[i].intensity = 1.0f-(progress-0.15f)/0.85f;

            m_particle[i].intensity *= 0.5f;

            ts.x = 0.25f;
            ts.y = 0.50f;
            ti.x = ts.x+0.25f;
            ti.y = ts.y+0.25f;
        }

        if (m_particle[i].type == PARTIERROR)
        {
            if (progress >= 1.0f)
            {
                DeleteRank(i);
                continue;
            }

            m_particle[i].zoom = progress*1.0f;
            m_particle[i].intensity = 1.0f-progress;

            ts.x = 0.500f;
            ts.y = 0.875f;
            ti.x = ts.x+0.125f;
            ti.y = ts.y+0.125f;
        }

        if (m_particle[i].type == PARTIWARNING)
        {
            if (progress >= 1.0f)
            {
                DeleteRank(i);
                continue;
            }

            m_particle[i].zoom = progress*1.0f;
            m_particle[i].intensity = 1.0f-progress;

            ts.x = 0.875f;
            ts.y = 0.875f;
            ti.x = ts.x+0.125f;
            ti.y = ts.y+0.125f;
        }

        if (m_particle[i].type == PARTIINFO)
        {
            if (progress >= 1.0f)
            {
                DeleteRank(i);
                continue;
            }

            m_particle[i].zoom = progress*1.0f;
            m_particle[i].intensity = 1.0f-progress;

            ts.x = 0.750f;
            ts.y = 0.875f;
            ti.x = ts.x+0.125f;
            ti.y = ts.y+0.125f;
        }

        if (m_particle[i].type == PARTISELY)
        {
            ts.x = 0.75f;
            ts.y = 0.25f;
            ti.x = ts.x+0.25f;
            ti.y = ts.y+0.25f;
        }
        if (m_particle[i].type == PARTISELR)
        {
            ts.x = 0.75f;
            ts.y = 0.00f;
            ti.x = ts.x+0.25f;
            ti.y = ts.y+0.25f;
        }

        if (m_particle[i].type == PARTISPHERE0)
        {
            if (progress >= 1.0f)
            {
                DeleteRank(i);
                continue;
            }

            m_particle[i].zoom = progress*m_particle[i].dim.x;

            if (progress < 0.65f)
                m_particle[i].intensity = progress/0.65f;
            else
                m_particle[i].intensity = 1.0f-(progress-0.65f)/0.35f;

            m_particle[i].intensity *= 0.5f;

            ts.x = 0.50f;
            ts.y = 0.75f;
            ti.x = ts.x+0.25f;
            ti.y = ts.y+0.25f;
        }

        if (m_particle[i].type == PARTISPHERE1)
        {
            if (progress >= 1.0f)
            {
                DeleteRank(i);
                continue;
            }

            if (progress < 0.30f)
                m_particle[i].intensity = progress/0.30f;
            else
                m_particle[i].intensity = 1.0f-(progress-0.30f)/0.70f;

            m_particle[i].zoom = progress*m_particle[i].dim.x;
            m_particle[i].angle = m_particle[i].time*Math::PI*2.0f;

            ts.x = 0.000f;
            ts.y = 0.000f;
            ti.x = ts.x+0.125f;
            ti.y = ts.y+0.125f;
        }

        if (m_particle[i].type == PARTISPHERE2)
        {
            if (progress >= 1.0f)
            {
                DeleteRank(i);
                continue;
            }

            if (progress < 0.20f)
                m_particle[i].intensity = 1.0f;
            else
                m_particle[i].intensity = 1.0f-(progress-0.20f)/0.80f;

            m_particle[i].zoom = progress*m_particle[i].dim.x;
            m_particle[i].angle = m_particle[i].time*Math::PI*2.0f;

            ts.x = 0.125f;
            ts.y = 0.000f;
            ti.x = ts.x+0.125f;
            ti.y = ts.y+0.125f;
        }

        if (m_particle[i].type == PARTISPHERE3)
        {
            if (m_particle[i].phase == PARPHEND &&
                progress >= 1.0f)
            {
                DeleteRank(i);
                continue;
            }

            if (m_particle[i].phase == PARPHSTART)
            {
                m_particle[i].intensity = progress;
                if (m_particle[i].intensity > 1.0f)
                    m_particle[i].intensity = 1.0f;
            }

            if (m_particle[i].phase == PARPHEND)
                m_particle[i].intensity = 1.0f-progress;

            m_particle[i].zoom = m_particle[i].dim.x;
            m_particle[i].angle = m_particle[i].time*Math::PI*0.2f;

            ts.x = 0.25f;
            ts.y = 0.75f;
            ti.x = ts.x+0.25f;
            ti.y = ts.y+0.25f;
        }

        if (m_particle[i].type == PARTISPHERE4)
        {
            if (progress >= 1.0f)
            {
                DeleteRank(i);
                continue;
            }

            m_particle[i].zoom = progress*m_particle[i].dim.x;

            if (progress < 0.65 )
                m_particle[i].intensity = progress/0.65f;
            else
                m_particle[i].intensity = 1.0f-(progress-0.65f)/0.35f;

            m_particle[i].intensity *= 0.5f;

            ts.x = 0.125f;
            ts.y = 0.000f;
            ti.x = ts.x+0.125f;
            ti.y = ts.y+0.125f;
        }

        if (m_particle[i].type == PARTISPHERE5)
        {
            m_particle[i].intensity = 0.7f+sinf(progress)*0.3f;
            m_particle[i].zoom = m_particle[i].dim.x*(1.0f+sinf(progress*0.7f)*0.01f);
            m_particle[i].angle = m_particle[i].time*Math::PI*0.2f;

            ts.x = 0.25f;
            ts.y = 0.50f;
            ti.x = ts.x+0.25f;
            ti.y = ts.y+0.25f;
        }

        if (m_particle[i].type == PARTISPHERE6)
        {
            if (progress >= 1.0f)
            {
                DeleteRank(i);
                continue;
            }

            m_particle[i].zoom = (1.0f-progress)*m_particle[i].dim.x;
            m_particle[i].intensity = progress*0.5f;

            ts.x = 0.125f;
            ts.y = 0.000f;
            ti.x = ts.x+0.125f;
            ti.y = ts.y+0.125f;
        }

        if (m_particle[i].type == PARTIPLOUF0)
        {
            if (progress >= 1.0f)
            {
                DeleteRank(i);
                continue;
            }

            m_particle[i].zoom = progress;
            m_particle[i].intensity = 1.0f-progress;

            ts.x = 0.50f;
            ts.y = 0.50f;
            ti.x = ts.x+0.25f;
            ti.y = ts.y+0.25f;
        }

        if (m_particle[i].type == PARTIDROP)
        {
            if (progress >= 1.0f ||
                m_particle[i].pos.y < m_water->GetLevel())
            {
                DeleteRank(i);
                continue;
            }

            m_particle[i].zoom = 1.0f-progress;
            m_particle[i].intensity = 1.0f-progress;

            ts.x = 0.750f;
            ts.y = 0.500f;
            ti.x = ts.x+0.125f;
            ti.y = ts.y+0.125f;
        }

        if (m_particle[i].type == PARTIWATER)
        {
            if (progress >= 1.0f ||
                m_particle[i].pos.y < m_water->GetLevel())
            {
                DeleteRank(i);
                continue;
            }

            m_particle[i].intensity = 1.0f-progress;

            ts.x = 0.125f;
            ts.y = 0.125f;
            ti.x = ts.x+0.125f;
            ti.y = ts.y+0.125f;
        }

        if (m_particle[i].type == PARTIRAY1)  // tower ray ?
        {
            if (progress >= 1.0f)
            {
                DeleteRank(i);
                continue;
            }

            if (m_particle[i].testTime >= 0.2f)
            {
                m_particle[i].testTime = 0.0f;
                CObject* object = SearchObjectRay(m_particle[i].pos, m_particle[i].goal,
                                         m_particle[i].type, m_particle[i].objFather);
                if (object != nullptr)
                {
                    assert(object->Implements(ObjectInterfaceType::Damageable));
                    dynamic_cast<CDamageableObject&>(*object).DamageObject(DamageType::Tower, std::numeric_limits<float>::infinity(), m_particle[i].objFather);
                }
            }

            ts.x = 0.00f;
            ts.y = 0.00f;
            ti.x = ts.x+0.25f;
            ti.y = ts.y+0.25f;
        }

        if (m_particle[i].type == PARTIRAY2 ||
            m_particle[i].type == PARTIRAY3)
        {
            if (progress >= 1.0f)
            {
                DeleteRank(i);
                continue;
            }

            ts.x = 0.00f;
            ts.y = 0.25f;
            ti.x = ts.x+0.25f;
            ti.y = ts.y+0.25f;
        }

        float dp = (1.0f/256.0f)/2.0f;
        m_particle[i].texSup.x = ts.x+dp;
        m_particle[i].texSup.y = ts.y+dp;
        m_particle[i].texInf.x = ti.x-dp;
        m_particle[i].texInf.y = ti.y-dp;
        m_particle[i].time     += rTime;
        m_particle[i].testTime += rTime;
    }
}

bool CParticle::TrackMove(int i, Math::Vector pos, float progress)
{
    if (i < 0 || i >= MAXTRACK)  return true;
    if (! m_track[i].used) return true;

    if (progress < 1.0f)  // particle exists?
    {
        int h = m_track[i].head;

        Math::Vector last;

        if ( m_track[i].posUsed == 1 ||
             m_track[i].last+m_track[i].step <= progress )
        {
            m_track[i].last = progress;
            last = m_track[i].pos[h];
            h ++;
            if (h == MAXTRACKLEN)  h = 0;
            if (m_track[i].posUsed < MAXTRACKLEN)  m_track[i].posUsed++;
        }
        else
        {
            int hh = h-1;
            if (hh < 0)  hh = MAXTRACKLEN-1;
            last = m_track[i].pos[hh];
        }
        m_track[i].pos[h] = pos;
        m_track[i].len[h] = Math::Distance(pos, last);

        m_track[i].head = h;

        m_track[i].intensity = 1.0f-progress;
    }
    else    // slow death of the track ?
    {
        m_track[i].intensity = 0.0f;
    }

    return (m_track[i].intensity <= 0.0f);
}

void CParticle::TrackDraw(int i, ParticleType type)
{
    // Calculates the total length memorized.
    float lTotal = 0.0f;
    int h = m_track[i].head;
    for (int counter = 0; counter < m_track[i].posUsed-1; counter++)
    {
        lTotal += m_track[i].len[h];
        h--;
        if (h < 0) h = MAXTRACKLEN-1;
    }

    Math::Matrix mat;
    mat.LoadIdentity();
    m_device->SetTransform(TRANSFORM_WORLD, mat);

    Math::Point texInf, texSup;

    if (type == PARTITRACK1)  // technical explosion?
    {
        texInf.x = 64.5f/256.0f;
        texInf.y = 21.0f/256.0f;
        texSup.x = 95.5f/256.0f;
        texSup.y = 22.0f/256.0f;  // orange
    }
    if (type == PARTITRACK2)  // blue spray?
    {
        texInf.x = 64.5f/256.0f;
        texInf.y = 13.0f/256.0f;
        texSup.x = 95.5f/256.0f;
        texSup.y = 14.0f/256.0f;  // blue
    }
    if (type == PARTITRACK3)  // spider?
    {
        texInf.x = 64.5f/256.0f;
        texInf.y =  5.0f/256.0f;
        texSup.x = 95.5f/256.0f;
        texSup.y =  6.0f/256.0f;  // brown
    }
    if (type == PARTITRACK4)  // insect explosion?
    {
        texInf.x = 64.5f/256.0f;
        texInf.y =  9.0f/256.0f;
        texSup.x = 95.5f/256.0f;
        texSup.y = 10.0f/256.0f;  // dark green
    }
    if (type == PARTITRACK5)  // derrick?
    {
        texInf.x = 64.5f/256.0f;
        texInf.y = 29.0f/256.0f;
        texSup.x = 95.5f/256.0f;
        texSup.y = 30.0f/256.0f;  // dark brown
    }
    if (type == PARTITRACK6)  // reset in/out?
    {
        texInf.x = 64.5f/256.0f;
        texInf.y = 17.0f/256.0f;
        texSup.x = 95.5f/256.0f;
        texSup.y = 18.0f/256.0f;  // cyan
    }
    if (type == PARTITRACK7)  // win-1?
    {
        texInf.x = 64.5f/256.0f;
        texInf.y = 41.0f/256.0f;
        texSup.x = 95.5f/256.0f;
        texSup.y = 42.0f/256.0f;  // orange
    }
    if (type == PARTITRACK8)  // win-2?
    {
        texInf.x = 64.5f/256.0f;
        texInf.y = 45.0f/256.0f;
        texSup.x = 95.5f/256.0f;
        texSup.y = 46.0f/256.0f;  // yellow
    }
    if (type == PARTITRACK9)  // win-3?
    {
        texInf.x = 64.5f/256.0f;
        texInf.y = 49.0f/256.0f;
        texSup.x = 95.5f/256.0f;
        texSup.y = 50.0f/256.0f;  // red
    }
    if (type == PARTITRACK10)  // win-4?
    {
        texInf.x = 64.5f/256.0f;
        texInf.y = 53.0f/256.0f;
        texSup.x = 95.5f/256.0f;
        texSup.y = 54.0f/256.0f;  // violet
    }
    if (type == PARTITRACK11)  // phazer shot?
    {
        texInf.x = 64.5f/256.0f;
        texInf.y = 21.0f/256.0f;
        texSup.x = 95.5f/256.0f;
        texSup.y = 22.0f/256.0f;  // orange
    }
    if (type == PARTITRACK12)  // drag reactor?
    {
        texInf.x = 64.5f/256.0f;
        texInf.y = 21.0f/256.0f;
        texSup.x = 95.5f/256.0f;
        texSup.y = 22.0f/256.0f;  // orange
    }

    h  = m_track[i].head;
    Math::Vector p1 = m_track[i].pos[h];
    float f1 = m_track[i].intensity;

    Math::Vector eye = m_engine->GetEyePt();
    float a = Math::RotateAngle(eye.x-p1.x, eye.z-p1.z);

    Vertex vertex[4];
    Math::Vector corner[4];

    for (int counter = 0; counter < m_track[i].posUsed-1; counter++)
    {
        float f2 = f1-(m_track[i].len[h]/lTotal);
        if (f2 < 0.0f) f2 = 0.0f;

        h --;
        if (h < 0) h = MAXTRACKLEN-1;

        Math::Vector p2 = m_track[i].pos[h];

        Math::Vector n = Normalize(p1-eye);

        Math::Vector p;
        Math::Point rot;

        p = p1;
        p.x += f1*m_track[i].width;
        rot = Math::RotatePoint(Math::Point(p1.x, p1.z), a+Math::PI/2.0f, Math::Point(p.x, p.z));
        corner[0].x = rot.x;
        corner[0].y = p1.y;
        corner[0].z = rot.y;
        rot = Math::RotatePoint(Math::Point(p1.x, p1.z), a-Math::PI/2.0f, Math::Point(p.x, p.z));
        corner[1].x = rot.x;
        corner[1].y = p1.y;
        corner[1].z = rot.y;

        p = p2;
        p.x += f2*m_track[i].width;
        rot = Math::RotatePoint(Math::Point(p2.x, p2.z), a+Math::PI/2.0f, Math::Point(p.x, p.z));
        corner[2].x = rot.x;
        corner[2].y = p2.y;
        corner[2].z = rot.y;
        rot = Math::RotatePoint(Math::Point(p2.x, p2.z), a-Math::PI/2.0f, Math::Point(p.x, p.z));
        corner[3].x = rot.x;
        corner[3].y = p2.y;
        corner[3].z = rot.y;

        if (p2.y < p1.y)
        {
            vertex[0] = Vertex(corner[1], n, Math::Point(texSup.x, texSup.y));
            vertex[1] = Vertex(corner[0], n, Math::Point(texInf.x, texSup.y));
            vertex[2] = Vertex(corner[3], n, Math::Point(texSup.x, texInf.y));
            vertex[3] = Vertex(corner[2], n, Math::Point(texInf.x, texInf.y));
        }
        else
        {
            vertex[0] = Vertex(corner[0], n, Math::Point(texSup.x, texSup.y));
            vertex[1] = Vertex(corner[1], n, Math::Point(texInf.x, texSup.y));
            vertex[2] = Vertex(corner[2], n, Math::Point(texSup.x, texInf.y));
            vertex[3] = Vertex(corner[3], n, Math::Point(texInf.x, texInf.y));
        }

        m_device->DrawPrimitive(PRIMITIVE_TRIANGLE_STRIP, vertex, 4);
        m_engine->AddStatisticTriangle(2);

        if (f2 < 0.0f) break;
        f1 = f2;
        p1 = p2;
    }
}

void CParticle::DrawParticleTriangle(int i)
{
    if (m_particle[i].zoom == 0.0f)  return;

    Math::Vector eye = m_engine->GetEyePt();
    Math::Vector pos = m_particle[i].pos;

    CObject* object = m_particle[i].objLink;
    if (object != nullptr)
        pos += object->GetPosition();

    Math::Vector angle;
    angle.x = -Math::RotateAngle(Math::DistanceProjected(pos, eye), pos.y-eye.y);
    angle.y = Math::RotateAngle(pos.z-eye.z, pos.x-eye.x);
    angle.z = m_particle[i].angle;

    Math::Matrix mat;
    Math::LoadRotationXZYMatrix(mat, angle);
    mat.Set(1, 4, pos.x);
    mat.Set(2, 4, pos.y);
    mat.Set(3, 4, pos.z);
    m_device->SetTransform(TRANSFORM_WORLD, mat);

    m_device->DrawPrimitive(PRIMITIVE_TRIANGLES, m_triangle[i].triangle, 3);
    m_engine->AddStatisticTriangle(1);
}

void CParticle::DrawParticleNorm(int i)
{
    float zoom = m_particle[i].zoom;

    if (zoom == 0.0f) return;
    if (m_particle[i].intensity == 0.0f) return;


    Math::Vector corner[4];
    Vertex vertex[4];

    if (m_particle[i].sheet == SH_INTERFACE)
    {
        Math::Vector pos = m_particle[i].pos;

        Math::Vector n(0.0f, 0.0f, -1.0f);

        Math::Point dim;
        dim.x = m_particle[i].dim.x * zoom;
        dim.y = m_particle[i].dim.y * zoom;

        corner[0].x = pos.x+dim.x;
        corner[0].y = pos.y+dim.y;
        corner[0].z = 0.0f;

        corner[1].x = pos.x-dim.x;
        corner[1].y = pos.y+dim.y;
        corner[1].z = 0.0f;

        corner[2].x = pos.x+dim.x;
        corner[2].y = pos.y-dim.y;
        corner[2].z = 0.0f;

        corner[3].x = pos.x-dim.x;
        corner[3].y = pos.y-dim.y;
        corner[3].z = 0.0f;

        vertex[0] = Vertex(corner[1], n, Math::Point(m_particle[i].texSup.x, m_particle[i].texSup.y));
        vertex[1] = Vertex(corner[0], n, Math::Point(m_particle[i].texInf.x, m_particle[i].texSup.y));
        vertex[2] = Vertex(corner[3], n, Math::Point(m_particle[i].texSup.x, m_particle[i].texInf.y));
        vertex[3] = Vertex(corner[2], n, Math::Point(m_particle[i].texInf.x, m_particle[i].texInf.y));

        m_device->DrawPrimitive(PRIMITIVE_TRIANGLE_STRIP, vertex, 4);
        m_engine->AddStatisticTriangle(2);
    }
    else
    {
        Math::Vector eye = m_engine->GetEyePt();
        Math::Vector pos = m_particle[i].pos;

        CObject* object = m_particle[i].objLink;
        if (object != nullptr)
            pos += object->GetPosition();

        Math::Vector angle;
        angle.x = -Math::RotateAngle(Math::DistanceProjected(pos, eye), pos.y-eye.y);
        angle.y = Math::RotateAngle(pos.z-eye.z, pos.x-eye.x);
        angle.z = m_particle[i].angle;

        Math::Matrix mat;
        Math::LoadRotationXZYMatrix(mat, angle);
        mat.Set(1, 4, pos.x);
        mat.Set(2, 4, pos.y);
        mat.Set(3, 4, pos.z);
        m_device->SetTransform(TRANSFORM_WORLD, mat);

        Math::Vector n(0.0f, 0.0f, -1.0f);

        Math::Point dim;
        dim.x = m_particle[i].dim.x * zoom;
        dim.y = m_particle[i].dim.y * zoom;

        corner[0].x =  dim.x;
        corner[0].y =  dim.y;
        corner[0].z =  0.0f;

        corner[1].x = -dim.x;
        corner[1].y =  dim.y;
        corner[1].z =  0.0f;

        corner[2].x =  dim.x;
        corner[2].y = -dim.y;
        corner[2].z =  0.0f;

        corner[3].x = -dim.x;
        corner[3].y = -dim.y;
        corner[3].z =  0.0f;

        vertex[0] = Vertex(corner[1], n, Math::Point(m_particle[i].texSup.x, m_particle[i].texSup.y));
        vertex[1] = Vertex(corner[0], n, Math::Point(m_particle[i].texInf.x, m_particle[i].texSup.y));
        vertex[2] = Vertex(corner[3], n, Math::Point(m_particle[i].texSup.x, m_particle[i].texInf.y));
        vertex[3] = Vertex(corner[2], n, Math::Point(m_particle[i].texInf.x, m_particle[i].texInf.y));

        m_device->DrawPrimitive(PRIMITIVE_TRIANGLE_STRIP, vertex, 4, m_particle[i].color);
        m_engine->AddStatisticTriangle(2);
    }
}

void CParticle::DrawParticleFlat(int i)
{
    if (m_particle[i].zoom == 0.0f) return;
    if (m_particle[i].intensity == 0.0f) return;

    Math::Vector pos = m_particle[i].pos;

    CObject* object = m_particle[i].objLink;
    if (object != nullptr)
        pos += object->GetPosition();

    Math::Vector angle;
    angle.x = Math::PI/2.0f;
    angle.y = 0.0f;
    angle.z = m_particle[i].angle;

    if (m_engine->GetRankView() == 1)  // underwater?
        pos.y -= 1.0f;

    Math::Vector eye = m_engine->GetEyePt();
    if (pos.y > eye.y)  // seen from below?
        angle.x = -Math::PI/2.0f;

    Math::Matrix mat;
    Math::LoadRotationXZYMatrix(mat, angle);
    mat.Set(1, 4, pos.x);
    mat.Set(2, 4, pos.y);
    mat.Set(3, 4, pos.z);
    m_device->SetTransform(TRANSFORM_WORLD, mat);

    Math::Vector n(0.0f, 0.0f, -1.0f);

    Math::Point dim;
    dim.x = m_particle[i].dim.x * m_particle[i].zoom;
    dim.y = m_particle[i].dim.y * m_particle[i].zoom;

    Math::Vector corner[4];
    corner[0].x =  dim.x;
    corner[0].y =  dim.y;
    corner[0].z =  0.0f;

    corner[1].x = -dim.x;
    corner[1].y =  dim.y;
    corner[1].z =  0.0f;

    corner[2].x =  dim.x;
    corner[2].y = -dim.y;
    corner[2].z =  0.0f;

    corner[3].x = -dim.x;
    corner[3].y = -dim.y;
    corner[3].z =  0.0f;

    Vertex vertex[4];
    vertex[0] = Vertex(corner[1], n, Math::Point(m_particle[i].texSup.x, m_particle[i].texSup.y));
    vertex[1] = Vertex(corner[0], n, Math::Point(m_particle[i].texInf.x, m_particle[i].texSup.y));
    vertex[2] = Vertex(corner[3], n, Math::Point(m_particle[i].texSup.x, m_particle[i].texInf.y));
    vertex[3] = Vertex(corner[2], n, Math::Point(m_particle[i].texInf.x, m_particle[i].texInf.y));

    m_device->DrawPrimitive(PRIMITIVE_TRIANGLE_STRIP, vertex, 4);
    m_engine->AddStatisticTriangle(2);
}

void CParticle::DrawParticleFog(int i)
{
    if (!m_engine->GetFog()) return;
    if (m_particle[i].intensity == 0.0f) return;

    Math::Vector pos = m_particle[i].pos;

    Math::Point dim;
    dim.x = m_particle[i].dim.x;
    dim.y = m_particle[i].dim.y;

    Math::Point zoom;

    if ( m_particle[i].type == PARTIFOG0 ||
         m_particle[i].type == PARTIFOG2 ||
         m_particle[i].type == PARTIFOG4 ||
         m_particle[i].type == PARTIFOG6 )
    {
        zoom.x = 1.0f+sinf(m_particle[i].zoom*2.0f)/6.0f;
        zoom.y = 1.0f+cosf(m_particle[i].zoom*2.7f)/6.0f;
    }
    if ( m_particle[i].type == PARTIFOG1 ||
         m_particle[i].type == PARTIFOG3 ||
         m_particle[i].type == PARTIFOG5 ||
         m_particle[i].type == PARTIFOG7 )
    {
        zoom.x = 1.0f+sinf(m_particle[i].zoom*3.0f)/6.0f;
        zoom.y = 1.0f+cosf(m_particle[i].zoom*3.7f)/6.0f;
    }

    dim.x *= zoom.x;
    dim.y *= zoom.y;

    CObject* object = m_particle[i].objLink;
    if (object != nullptr)
        pos += object->GetPosition();

    Math::Vector angle;
    angle.x = Math::PI/2.0f;
    angle.y = 0.0f;
    angle.z = m_particle[i].angle;

    if (m_engine->GetRankView() == 1)  // underwater?
        pos.y -= 1.0f;

    Math::Vector eye = m_engine->GetEyePt();
    if (pos.y > eye.y)  // seen from below?
        angle.x = -Math::PI/2.0f;

    Math::Matrix mat;
    Math::LoadRotationXZYMatrix(mat, angle);
    mat.Set(1, 4, pos.x);
    mat.Set(2, 4, pos.y);
    mat.Set(3, 4, pos.z);
    m_device->SetTransform(TRANSFORM_WORLD, mat);

    Math::Vector n(0.0f, 0.0f, -1.0f);

    Math::Vector corner[4];

    corner[0].x =  dim.x;
    corner[0].y =  dim.y;
    corner[0].z =  0.0f;

    corner[1].x = -dim.x;
    corner[1].y =  dim.y;
    corner[1].z =  0.0f;

    corner[2].x =  dim.x;
    corner[2].y = -dim.y;
    corner[2].z =  0.0f;

    corner[3].x = -dim.x;
    corner[3].y = -dim.y;
    corner[3].z =  0.0f;

    Vertex vertex[4];

    vertex[0] = Vertex(corner[1], n, Math::Point(m_particle[i].texSup.x, m_particle[i].texSup.y));
    vertex[1] = Vertex(corner[0], n, Math::Point(m_particle[i].texInf.x, m_particle[i].texSup.y));
    vertex[2] = Vertex(corner[3], n, Math::Point(m_particle[i].texSup.x, m_particle[i].texInf.y));
    vertex[3] = Vertex(corner[2], n, Math::Point(m_particle[i].texInf.x, m_particle[i].texInf.y));

    m_device->DrawPrimitive(PRIMITIVE_TRIANGLE_STRIP, vertex, 4);
    m_engine->AddStatisticTriangle(2);
}

void CParticle::DrawParticleRay(int i)
{
    if (m_particle[i].zoom == 0.0f)  return;
    if (m_particle[i].intensity == 0.0f)  return;

    Math::Vector eye = m_engine->GetEyePt();
    Math::Vector pos = m_particle[i].pos;
    Math::Vector goal = m_particle[i].goal;

    CObject* object = m_particle[i].objLink;
    if (object != nullptr)
        pos += object->GetPosition();

    float a = Math::RotateAngle(Math::Point(pos.x,pos.z), Math::Point(goal.x,goal.z), Math::Point(eye.x,eye.z));
    bool left = (a < Math::PI);

    Math::Vector proj = Math::Projection(pos, goal, eye);
    Math::Vector angle;
    angle.x = -Math::RotateAngle(Math::DistanceProjected(proj, eye), proj.y-eye.y);
    angle.y = Math::RotateAngle(pos.z-goal.z, pos.x-goal.x)+Math::PI/2.0f;
    angle.z = -Math::RotateAngle(Math::DistanceProjected(pos, goal), pos.y-goal.y);
    if (left)  angle.x = -angle.x;

    Math::Matrix mat;
    Math::LoadRotationZXYMatrix(mat, angle);
    mat.Set(1, 4, pos.x);
    mat.Set(2, 4, pos.y);
    mat.Set(3, 4, pos.z);
    m_device->SetTransform(TRANSFORM_WORLD, mat);

    Math::Vector n(0.0f, 0.0f, left ? 1.0f : -1.0f);

    Math::Point dim;
    dim.x = m_particle[i].dim.x * m_particle[i].zoom;
    dim.y = m_particle[i].dim.y * m_particle[i].zoom;

    if (left) dim.y = -dim.y;

    float len = Math::Distance(pos, goal);
    float adv = 0.0f;

    int step = static_cast<int>((len/(dim.x*2.0f))+1);

    float vario1, vario2;

    if (step == 1)
    {
        vario1 = 1.0f;
        vario2 = 1.0f;
    }
    else
    {
        vario1 = 0.0f;
        vario2 = 2.0f;
    }

    int first, last;

    if (m_particle[i].type == PARTIRAY2)
    {
        first = 0;
        last  = step;
        vario1 = 0.0f;
        vario2 = 0.0f;
    }
    else if (m_particle[i].type == PARTIRAY3)
    {
        if (m_particle[i].time < m_particle[i].duration*0.40f)
        {
            float prop = m_particle[i].time / (m_particle[i].duration*0.40f);
            first = 0;
            last  = static_cast<int>(prop*step);
        }
        else if (m_particle[i].time < m_particle[i].duration*0.60f)
        {
            first = 0;
            last  = step;
        }
        else
        {
            float prop = (m_particle[i].time-m_particle[i].duration*0.60f) / (m_particle[i].duration*0.40f);
            first = static_cast<int>(prop*step);
            last  = step;
        }
    }
    else
    {
        if (m_particle[i].time < m_particle[i].duration*0.50f)
        {
            float prop = m_particle[i].time / (m_particle[i].duration*0.50f);
            first = 0;
            last  = static_cast<int>(prop*step);
        }
        else if (m_particle[i].time < m_particle[i].duration*0.75f)
        {
            first = 0;
            last  = step;
        }
        else
        {
            float prop = (m_particle[i].time-m_particle[i].duration*0.75f) / (m_particle[i].duration*0.25f);
            first = static_cast<int>(prop*step);
            last  = step;
        }
    }

    Math::Vector corner[4];

    corner[0].x = adv;
    corner[2].x = adv;
    corner[0].y =  dim.y;
    corner[2].y = -dim.y;
    corner[0].z = (Math::Rand()-0.5f)*vario1;
    corner[1].z = (Math::Rand()-0.5f)*vario1;
    corner[2].z = (Math::Rand()-0.5f)*vario1;
    corner[3].z = (Math::Rand()-0.5f)*vario1;

    Vertex vertex[4];

    for (int rank = 0; rank < step; rank++)
    {
        corner[1].x = corner[0].x;
        corner[3].x = corner[2].x;
        corner[0].x = adv+dim.x*2.0f+(Math::Rand()-0.5f)*vario2;
        corner[2].x = adv+dim.x*2.0f+(Math::Rand()-0.5f)*vario2;

        corner[1].y = corner[0].y;
        corner[3].y = corner[2].y;
        corner[0].y =  dim.y+(Math::Rand()-0.5f)*vario2;
        corner[2].y = -dim.y+(Math::Rand()-0.5f)*vario2;

        if (rank >= first && rank <= last)
        {
            Math::Point texInf = m_particle[i].texInf;
            Math::Point texSup = m_particle[i].texSup;

            int r = rand() % 16;
            texInf.x += 0.25f*(r/4);
            texSup.x += 0.25f*(r/4);
            if (r % 2 < 1 && adv > 0.0f && m_particle[i].type != PARTIRAY1)
                Math::Swap(texInf.x, texSup.x);

            if (r % 4 < 2)
                Math::Swap(texInf.y, texSup.y);

            vertex[0] = Vertex(corner[1], n, Math::Point(texSup.x, texSup.y));
            vertex[1] = Vertex(corner[0], n, Math::Point(texInf.x, texSup.y));
            vertex[2] = Vertex(corner[3], n, Math::Point(texSup.x, texInf.y));
            vertex[3] = Vertex(corner[2], n, Math::Point(texInf.x, texInf.y));

            m_device->DrawPrimitive(PRIMITIVE_TRIANGLE_STRIP, vertex, 4);
            m_engine->AddStatisticTriangle(2);
        }
        adv += dim.x*2.0f;
    }
}

void CParticle::DrawParticleSphere(int i)
{
    float zoom = m_particle[i].zoom;

    if (zoom == 0.0f) return;

    m_engine->SetState(ENG_RSTATE_TTEXTURE_BLACK | ENG_RSTATE_2FACE | ENG_RSTATE_WRAP,
                       IntensityToColor(m_particle[i].intensity));

    Math::Matrix mat;
    mat.LoadIdentity();
    mat.Set(1, 1, zoom);
    mat.Set(2, 2, zoom);
    mat.Set(3, 3, zoom);
    mat.Set(1, 4, m_particle[i].pos.x);
    mat.Set(2, 4, m_particle[i].pos.y);
    mat.Set(3, 4, m_particle[i].pos.z);

    if (m_particle[i].angle != 0.0f)
    {
        Math::Vector angle;
        angle.x = m_particle[i].angle*0.4f;
        angle.y = m_particle[i].angle*1.0f;
        angle.z = m_particle[i].angle*0.7f;
        Math::Matrix rot;
        Math::LoadRotationZXYMatrix(rot, angle);
        mat = Math::MultiplyMatrices(mat, rot);
    }

    m_device->SetTransform(TRANSFORM_WORLD, mat);

    Math::Point ts, ti;
    ts.x = m_particle[i].texSup.x;
    ts.y = m_particle[i].texSup.y;
    ti.x = m_particle[i].texInf.x;
    ti.y = m_particle[i].texInf.y;

    int numRings, numSegments;

    // Choose a tesselation level.
    if ( m_particle[i].type == PARTISPHERE3 ||
         m_particle[i].type == PARTISPHERE5 )
    {
        numRings    = 16;
        numSegments = 16;
    }
    else
    {
        numRings    = 8;
        numSegments = 10;
    }

    // Establish constants used in sphere generation.
    float deltaRingAngle = Math::PI/numRings;
    float deltaSegAngle  = 2.0f*Math::PI/numSegments;

    Vertex vertex[2*16*(16+1)];

    // Generate the group of rings for the sphere.
    int j = 0;
    for (int ring = 0; ring < numRings; ring++)
    {
        float r0   = sinf((ring+0)*deltaRingAngle);
        float r1   = sinf((ring+1)*deltaRingAngle);

        Math::Vector v0, v1;

        v0.y = cosf((ring+0)*deltaRingAngle);
        v1.y = cosf((ring+1)*deltaRingAngle);

        float tv0 = (ring+0)/static_cast<float>(numRings);
        float tv1 = (ring+1)/static_cast<float>(numRings);
        tv0 = ts.y+(ti.y-ts.y)*tv0;
        tv1 = ts.y+(ti.y-ts.y)*tv1;

        // Generate the group of segments for the current ring.
        for (int seg = 0; seg < numSegments+1; seg++)
        {
            v0.x = r0*sinf(seg*deltaSegAngle);
            v0.z = r0*cosf(seg*deltaSegAngle);
            v1.x = r1*sinf(seg*deltaSegAngle);
            v1.z = r1*cosf(seg*deltaSegAngle);

            // Add two vertices to the strip which makes up the sphere.
            float tu0 = (static_cast<float>(seg))/numSegments;
            tu0 = ts.x+(ti.x-ts.x)*tu0;
            float tu1 = tu0;

            vertex[j++] = Vertex(v0, v0, Math::Point(tu0, tv0));
            vertex[j++] = Vertex(v1, v1, Math::Point(tu1, tv1));
        }
    }

    m_device->DrawPrimitive(PRIMITIVE_TRIANGLE_STRIP, vertex, j);
    m_engine->AddStatisticTriangle(j);

    m_engine->SetState(ENG_RSTATE_TTEXTURE_BLACK, IntensityToColor(m_particle[i].intensity));
}

//! Returns the height depending on the progress
static float ProgressCylinder(float progress)
{
    if (progress < 0.5f)
        return 1.0f - (powf(1.0f-progress*2.0f, 2.0f));
    else
        return 1.0f - (powf(progress*2.0f-1.0f, 2.0f));
}

void CParticle::DrawParticleCylinder(int i)
{
    float progress = m_particle[i].zoom;
    float zoom = m_particle[i].dim.x;
    float diam = m_particle[i].dim.y;
    if (progress >= 1.0f || zoom == 0.0f)  return;

    m_engine->SetState(ENG_RSTATE_TTEXTURE_BLACK | ENG_RSTATE_2FACE | ENG_RSTATE_WRAP,
                       IntensityToColor(m_particle[i].intensity));

    Math::Matrix mat;
    mat.LoadIdentity();
    mat.Set(1, 1, zoom);
    mat.Set(2, 2, zoom);
    mat.Set(3, 3, zoom);
    mat.Set(1, 4, m_particle[i].pos.x);
    mat.Set(2, 4, m_particle[i].pos.y);
    mat.Set(3, 4, m_particle[i].pos.z);
    m_device->SetTransform(TRANSFORM_WORLD, mat);

    Math::Point ts, ti;
    ts.x = m_particle[i].texSup.x;
    ts.y = m_particle[i].texSup.y;
    ti.x = m_particle[i].texInf.x;
    ti.y = m_particle[i].texInf.y;

    int numRings = 5;
    int numSegments = 10;
    float deltaSegAngle  = 2.0f*Math::PI/numSegments;

    float h[6] = { 0.0f };
    float d[6] = { 0.0f };

    if (m_particle[i].type == PARTIPLOUF0)
    {
        float p1 = progress;  // front
        float p2 = powf(progress, 5.0f);  // back

        for (int ring = 0; ring <= numRings; ring++)
        {
            float pp = p2+(p1-p2)*(static_cast<float>(ring)/numRings);
            d[ring] = diam/zoom+pp*2.0f;
            h[ring] = ProgressCylinder(pp);
        }
    }

    Vertex vertex[2*5*(10+1)];

    int j = 0;
    for (int ring = 0; ring < numRings; ring++)
    {
        Math::Vector v0, v1;

        float r0   = 1.0f*d[ring+0];  // radius at the base
        float r1   = 1.0f*d[ring+1];  // radius at the top

        v0.y = 1.0f*h[ring+0];  // bottom
        v1.y = 1.0f*h[ring+1];  // top

        float tv0 = 1.0f-(ring+0)*(1.0f/numRings);
        float tv1 = 1.0f-(ring+1)*(1.0f/numRings);
        tv0 = ts.y+(ti.y-ts.y)*tv0;
        tv1 = ts.y+(ti.y-ts.y)*tv1;

        for (int seg = 0; seg < numSegments+1; seg++)
        {
            v0.x = r0*sinf(seg*deltaSegAngle);
            v0.z = r0*cosf(seg*deltaSegAngle);
            v1.x = r1*sinf(seg*deltaSegAngle);
            v1.z = r1*cosf(seg*deltaSegAngle);

            float tu0 = (seg % 2) ? 0.0f : 1.0f;
            tu0 = ts.x+(ti.x-ts.x)*tu0;
            float tu1 = tu0;

            vertex[j++] = Vertex(v0, v0, Math::Point(tu0, tv0));
            vertex[j++] = Vertex(v1, v1, Math::Point(tu1, tv1));
        }
    }

    m_device->DrawPrimitive(PRIMITIVE_TRIANGLE_STRIP, vertex, j);
    m_engine->AddStatisticTriangle(j);

    m_engine->SetState(ENG_RSTATE_TTEXTURE_BLACK, IntensityToColor(m_particle[i].intensity));
}

void CParticle::DrawParticleText(int i)
{
    CharTexture tex = m_engine->GetText()->GetCharTexture(static_cast<UTF8Char>(m_particle[i].text), FONT_STUDIO, FONT_SIZE_BIG*2.0f);
    if (tex.id == 0) return;

    m_device->SetTexture(0, tex.id);
    m_engine->SetState(ENG_RSTATE_TTEXTURE_ALPHA, IntensityToColor(m_particle[i].intensity));

    Math::IntPoint fontTextureSize = m_engine->GetText()->GetFontTextureSize();
    m_particle[i].texSup.x = static_cast<float>(tex.charPos.x) / fontTextureSize.x;
    m_particle[i].texSup.y = static_cast<float>(tex.charPos.y) / fontTextureSize.y;
    m_particle[i].texInf.x = static_cast<float>(tex.charPos.x + tex.charSize.x) / fontTextureSize.x;
    m_particle[i].texInf.y = static_cast<float>(tex.charPos.y + tex.charSize.y) / fontTextureSize.y;
    m_particle[i].color = Color(0.0f, 0.0f, 0.0f);

    DrawParticleNorm(i);
}

void CParticle::DrawParticleWheel(int i)
{
    float dist = Math::DistanceProjected(m_engine->GetEyePt(), m_wheelTrace[i].pos[0]);
    if (dist > 300.0f)  return;

    if (m_wheelTrace[i].color == TraceColor::BlackArrow || m_wheelTrace[i].color == TraceColor::RedArrow)
    {
        m_engine->SetTexture("textures/effect03.png");
        m_engine->SetState(ENG_RSTATE_ALPHA);

        Math::Vector pos[4];
        pos[0] = m_wheelTrace[i].pos[0];
        pos[1] = m_wheelTrace[i].pos[1];
        pos[2] = m_wheelTrace[i].pos[2];
        pos[3] = m_wheelTrace[i].pos[3];

        Math::Vector n(0.0f, 1.0f, 0.0f);

        Math::Point ts(160.0f/256.0f, 224.0f/256.0f);
        Math::Point ti(ts.x+16.0f/256.0f, ts.y+16.0f/256.0f);

        float dp = (1.0f/256.0f)/2.0f;
        ts.x = ts.x+dp;
        ts.y = ts.y+dp;
        ti.x = ti.x-dp;
        ti.y = ti.y-dp;

        Vertex vertex[4];
        vertex[0] = Vertex(pos[0], n, Math::Point(ts.x, ts.y));
        vertex[1] = Vertex(pos[1], n, Math::Point(ti.x, ts.y));
        vertex[2] = Vertex(pos[2], n, Math::Point(ts.x, ti.y));
        vertex[3] = Vertex(pos[3], n, Math::Point(ti.x, ti.y));

        m_device->DrawPrimitive(PRIMITIVE_TRIANGLE_STRIP, vertex, 4, TraceColorColor(m_wheelTrace[i].color));
        m_engine->AddStatisticTriangle(2);

        m_engine->SetState(ENG_RSTATE_OPAQUE_COLOR);
    }
    else
    {
        Math::Vector pos[4];
        pos[0] = m_wheelTrace[i].pos[0];
        pos[1] = m_wheelTrace[i].pos[1];
        pos[2] = m_wheelTrace[i].pos[2];
        pos[3] = m_wheelTrace[i].pos[3];

        Math::Vector n(0.0f, 1.0f, 0.0f);

        Vertex vertex[4];
        vertex[0] = Vertex(pos[0], n);
        vertex[1] = Vertex(pos[1], n);
        vertex[2] = Vertex(pos[2], n);
        vertex[3] = Vertex(pos[3], n);

        m_device->DrawPrimitive(PRIMITIVE_TRIANGLE_STRIP, vertex, 4, TraceColorColor(m_wheelTrace[i].color));
        m_engine->AddStatisticTriangle(2);
    }
}

void CParticle::DrawParticle(int sheet)
{
    // Draw the basic particles of triangles.
    if (m_totalInterface[0][sheet] > 0)
    {
        for (int i = 0; i < MAXPARTICULE; i++)
        {
            if (!m_particle[i].used)  continue;
            if (m_particle[i].sheet != sheet)  continue;
            if (m_particle[i].type == PARTIPART)  continue;

            m_engine->SetTexture(!m_triangle[i].tex1Name.empty() ? "textures/"+m_triangle[i].tex1Name : "");
            m_engine->SetMaterial(m_triangle[i].material);
            m_engine->SetState(m_triangle[i].state);
            DrawParticleTriangle(i);
        }
    }

    Material mat;
    mat.diffuse.r = 1.0f;
    mat.diffuse.g = 1.0f;
    mat.diffuse.b = 1.0f;  // white
    mat.ambient.r = 0.5f;
    mat.ambient.g = 0.5f;
    mat.ambient.b = 0.5f;
    m_engine->SetMaterial(mat);

    // Draw tire marks.
    if (m_wheelTraceTotal > 0 && sheet == SH_WORLD)
    {
        m_engine->SetState(ENG_RSTATE_OPAQUE_COLOR);
        Math::Matrix matrix;
        matrix.LoadIdentity();
        m_device->SetTransform(TRANSFORM_WORLD, matrix);

        for (int i = 0; i < m_wheelTraceTotal; i++)
            DrawParticleWheel(i);
    }

    for (int t = MAXPARTITYPE-1; t >= 1; t--)  // black behind!
    {
        if (m_totalInterface[t][sheet] == 0)  continue;

        bool loadTexture = false;

        int state;
        if (t == 4) state = ENG_RSTATE_TTEXTURE_WHITE;  // effect03.png
        else        state = ENG_RSTATE_TTEXTURE_BLACK;  // effect[00..02].png
        m_engine->SetState(state);

        for (int j = 0; j < MAXPARTICULE; j++)
        {
            int i = MAXPARTICULE*t+j;
            if (!m_particle[i].used)  continue;
            if (m_particle[i].sheet != sheet)  continue;

            if (!loadTexture && t != 5)
            {
                std::string name;
                NameParticle(name, t);
                m_engine->SetTexture("textures/"+name);
                loadTexture = true;
            }

            int r = m_particle[i].trackRank;
            if (r != -1)
            {
                m_engine->SetState(state);
                TrackDraw(r, m_particle[i].type);  // draws the drag
                if (!m_track[r].drawParticle)  continue;
            }

            m_engine->SetState(state, IntensityToColor(m_particle[i].intensity));

            if (m_particle[i].ray)  // ray?
            {
                DrawParticleRay(i);
            }
            else if ( m_particle[i].type == PARTIFLIC  ||  // circle in the water?
                      m_particle[i].type == PARTISHOW  ||
                      m_particle[i].type == PARTICHOC  ||
                      m_particle[i].type == PARTIGFLAT )
            {
                DrawParticleFlat(i);
            }
            else if ( m_particle[i].type >= PARTIFOG0 &&
                      m_particle[i].type <= PARTIFOG7 )
            {
                DrawParticleFog(i);
            }
            else if ( m_particle[i].type >= PARTISPHERE0 &&
                      m_particle[i].type <= PARTISPHERE6 )  // sphere?
            {
                DrawParticleSphere(i);
            }
            else if ( m_particle[i].type == PARTIPLOUF0 )  // cylinder?
            {
                DrawParticleCylinder(i);
            }
            else if ( m_particle[i].type == PARTIVIRUS )
            {
                DrawParticleText(i);
            }
            else    // normal?
            {
                DrawParticleNorm(i);
            }
        }
    }
}

CObject* CParticle::SearchObjectGun(Math::Vector old, Math::Vector pos,
                                    ParticleType type, CObject *father)
{
    if (m_main->GetMovieLock()) return nullptr;  // current movie?

    float min = 5.0f;
    if (type == PARTIGUN2) min = 2.0f;  // shooting insect?
    if (type == PARTIGUN3) min = 3.0f;  // suiciding spider?

    Math::Vector box1 = old;
    Math::Vector box2 = pos;
    if (box1.x > box2.x)  Math::Swap(box1.x, box2.x);  // box1 < box2
    if (box1.y > box2.y)  Math::Swap(box1.y, box2.y);
    if (box1.z > box2.z)  Math::Swap(box1.z, box2.z);
    box1.x -= min;
    box1.y -= min;
    box1.z -= min;
    box2.x += min;
    box2.y += min;
    box2.z += min;

    CObject* best = nullptr;
    float best_dist = std::numeric_limits<float>::infinity();
    bool shield = false;
    for (CObject* obj : CObjectManager::GetInstancePointer()->GetAllObjects())
    {
        if (!obj->GetDetectable()) continue;  // inactive?
        if (obj == father) continue;

        ObjectType oType = obj->GetType();

        if (oType == OBJECT_TOTO)  continue;

        if (type == PARTIGUN1)  // fireball shooting?
        {
            if (oType == OBJECT_MOTHER)  continue;
        }
        else if (type == PARTIGUN2)  // shooting insect?
        {
            if (IsAlien(obj->GetType()))  continue;
        }
        else if (type == PARTIGUN3)  // suiciding spider?
        {
            if (IsAlien(obj->GetType()))  continue;
        }
        else if (type == PARTIGUN4)  // orgaball shooting?
        {
            if (oType == OBJECT_MOTHER)  continue;
        }
        else if (type == PARTITRACK11)  // phazer shooting?
        {
        }
        else
        {
            continue;
        }
        if (!obj->Implements(ObjectInterfaceType::Damageable) && !obj->IsBulletWall())  continue;
        if (obj->Implements(ObjectInterfaceType::Jostleable))  continue;

        Math::Vector oPos = obj->GetPosition();

        if (obj->GetType() == OBJECT_MOBILErs)
        {
            CShielder* shielder = dynamic_cast<CShielder*>(obj);
            if ( type == PARTIGUN2 ||  // shooting insect?
                 type == PARTIGUN3 )   // suiciding spider?
            {
                // Test if the ball is entered into the sphere of a shield.
                float shieldRadius = shielder->GetActiveShieldRadius();
                if (shieldRadius > 0.0f)
                {
                    float dist = Math::Distance(oPos, pos);
                    if (dist <= shieldRadius)
                    {
                        best = obj;
                        shield = true;
                    }
                }
            }
        }
        if (shield)  continue;

        // Test the center of the object, which is necessary for objects
        // that have no sphere in the center (station).
        float dist = Math::Distance(oPos, pos)-4.0f;
        float obj_dist = Math::Distance(old, oPos);
        if (dist < min && obj_dist < best_dist)
        {
            best = obj;
            best_dist = obj_dist;
        }

        for (const auto& crashSphere : obj->GetAllCrashSpheres())
        {
            oPos = crashSphere.sphere.pos;
            float oRadius = crashSphere.sphere.radius;

            if ( oPos.x+oRadius < box1.x || oPos.x-oRadius > box2.x ||  // outside the box?
                 oPos.y+oRadius < box1.y || oPos.y-oRadius > box2.y ||
                 oPos.z+oRadius < box1.z || oPos.z-oRadius > box2.z )  continue;

            Math::Vector p = Math::Projection(old, pos, oPos);
            float ddist = Math::Distance(p, oPos)-oRadius;
            float obj_dist = Math::Distance(old, oPos);
            if (ddist < min && obj_dist < best_dist)
            {
                best = obj;
                best_dist = obj_dist;
            }
        }
    }

    return best;
}

CObject* CParticle::SearchObjectRay(Math::Vector pos, Math::Vector goal,
                                    ParticleType type, CObject *father)
{
    if (m_main->GetMovieLock()) return nullptr;  // current movie?

    float min = 10.0f;

    Math::Vector box1 = pos;
    Math::Vector box2 = goal;
    if (box1.x > box2.x)  Math::Swap(box1.x, box2.x);  // box1 < box2
    if (box1.y > box2.y)  Math::Swap(box1.y, box2.y);
    if (box1.z > box2.z)  Math::Swap(box1.z, box2.z);
    box1.x -= min;
    box1.y -= min;
    box1.z -= min;
    box2.x += min;
    box2.y += min;
    box2.z += min;

    for (CObject* obj : CObjectManager::GetInstancePointer()->GetAllObjects())
    {
        if (!obj->GetDetectable()) continue;  // inactive?
        if (obj == father) continue;

        ObjectType oType = obj->GetType();

        if (oType == OBJECT_TOTO)  continue;

        if ( type  == PARTIRAY1       &&
             oType != OBJECT_MOBILEtg &&
             oType != OBJECT_TEEN28   &&
             oType != OBJECT_TEEN31   &&
             oType != OBJECT_ANT      &&
             oType != OBJECT_SPIDER   &&
             oType != OBJECT_BEE      &&
             oType != OBJECT_WORM     &&
             oType != OBJECT_MOTHER   &&
             oType != OBJECT_NEST     )  continue;

        Math::Vector oPos = obj->GetPosition();

        if ( oPos.x < box1.x || oPos.x > box2.x ||  // outside the box?
             oPos.y < box1.y || oPos.y > box2.y ||
             oPos.z < box1.z || oPos.z > box2.z )  continue;

        Math::Vector p = Math::Projection(pos, goal, oPos);
        float dist = Math::Distance(p, oPos);
        if (dist < min)  return obj;
    }

    return nullptr;
}

void CParticle::Play(SoundType sound, Math::Vector pos, float amplitude)
{
    if (m_sound == nullptr)
        m_sound = CApplication::GetInstancePointer()->GetSound();

    m_sound->Play(sound, pos, amplitude);
}

Color CParticle::GetFogColor(Math::Vector pos)
{
    Color result;
    result.r = 0.0f;
    result.g = 0.0f;
    result.b = 0.0f;
    result.a = 0.0f;

    for (int fog = 0; fog < m_fogTotal; fog++)
    {
        int i = m_fog[fog];  // i = rank of the particle

        if (pos.y >= m_particle[i].pos.y+FOG_HSUP)  continue;
        if (pos.y <= m_particle[i].pos.y-FOG_HINF)  continue;

        float dist = Math::DistanceProjected(pos, m_particle[i].pos);
        if (dist >= m_particle[i].dim.x*1.5f)  continue;

        // Calculates the horizontal distance.
        float factor = 1.0f-powf(dist/(m_particle[i].dim.x*1.5f), 4.0f);

        // Calculates the vertical distance.
        if (pos.y > m_particle[i].pos.y)
            factor *= 1.0f-(pos.y-m_particle[i].pos.y)/FOG_HSUP;
        else
            factor *= 1.0f-(m_particle[i].pos.y-pos.y)/FOG_HINF;

        factor *= 0.3f;

        Color color;

        if ( m_particle[i].type == PARTIFOG0 ||
             m_particle[i].type == PARTIFOG1 )  // blue?
        {
            color.r = 0.0f;
            color.g = 0.5f;
            color.b = 1.0f;
        }
        else if ( m_particle[i].type == PARTIFOG2 ||
                  m_particle[i].type == PARTIFOG3 )  // red?
        {
            color.r = 2.0f;
            color.g = 1.0f;
            color.b = 0.0f;
        }
        else if ( m_particle[i].type == PARTIFOG4 ||
                  m_particle[i].type == PARTIFOG5 )  // white?
        {
            color.r = 1.0f;
            color.g = 1.0f;
            color.b = 1.0f;
        }
        else if ( m_particle[i].type == PARTIFOG6 ||
                  m_particle[i].type == PARTIFOG7 )  // yellow?
        {
            color.r = 0.8f;
            color.g = 1.0f;
            color.b = 0.4f;
        }
        else
        {
            color.r = 0.0f;
            color.g = 0.0f;
            color.b = 0.0f;
        }

        result.r += color.r*factor;
        result.g += color.g*factor;
        result.b += color.b*factor;
    }

    if (result.r > 0.6f)  result.r = 0.6f;
    if (result.g > 0.6f)  result.g = 0.6f;
    if (result.b > 0.6f)  result.b = 0.6f;

    return result;
}

void CParticle::CutObjectLink(CObject* obj)
{
    for (int i = 0; i < MAXPARTICULE*MAXPARTITYPE; i++)
    {
        if (!m_particle[i].used) continue;

        if (m_particle[i].objLink == obj)
        {
            // If the object this particle's coordinates are linked to doesn't exist anymore, remove the particle
            DeleteRank(i);
        }

        if (m_particle[i].objFather == obj)
        {
            // If the object that spawned this partcle doesn't exist anymore, remove the link
            m_particle[i].objFather = nullptr;
        }
    }
}

} // namespace Gfx
