/*
 * Animation plugin for compiz/beryl
 *
 * animation.c
 *
 * Copyright : (C) 2006 Erkin Bahceci
 * E-mail    : erkinbah@gmail.com
 *
 * Based on Wobbly and Minimize plugins by
 *           : David Reveman
 * E-mail    : davidr@novell.com>
 *
 * Particle system added by : (C) 2006 Dennis Kasprzyk
 * E-mail                   : onestone@beryl-project.org
 *
 * Beam-Up added by : Florencio Guimaraes
 * E-mail           : florencio@nexcorp.com.br
 *
 * Hexagon tessellator added by : Mike Slegeir
 * E-mail                       : mikeslegeir@mail.utexas.edu>
 *
 * 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 2
 * 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, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

#include "animation-internal.h"

void initParticles(int numParticles, ParticleSystem * ps)
{
    if (ps->particles)
	free(ps->particles);
    ps->particles = calloc(numParticles, sizeof(Particle));
    ps->tex = 0;
    ps->numParticles = numParticles;
    ps->slowdown = 1;
    ps->active = FALSE;

    // Initialize cache
    ps->vertices_cache = NULL;
    ps->colors_cache = NULL;
    ps->coords_cache = NULL;
    ps->dcolors_cache = NULL;
    ps->vertex_cache_count = 0;
    ps->color_cache_count = 0;
    ps->coords_cache_count = 0;
    ps->dcolors_cache_count = 0;

    int i;

    for (i = 0; i < numParticles; i++)
    {
	ps->particles[i].life = 0.0f;
    }
}

void drawParticles(CompScreen * s, CompWindow * w, ParticleSystem * ps)
{
    glPushMatrix();
    if (w)
	glTranslated(WIN_X(w) - ps->x, WIN_Y(w) - ps->y, 0);

    glEnable(GL_BLEND);
    if (ps->tex)
    {
	glBindTexture(GL_TEXTURE_2D, ps->tex);
	glEnable(GL_TEXTURE_2D);
    }
    glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);

    int i;
    Particle *part;

    /* Check that the cache is big enough */
    if (ps->numParticles > ps->vertex_cache_count)
    {
	ps->vertices_cache =
	    realloc(ps->vertices_cache,
		    ps->numParticles * 4 * 3 * sizeof(GLfloat));
	ps->vertex_cache_count = ps->numParticles;
    }

    if (ps->numParticles > ps->coords_cache_count)
    {
	ps->coords_cache =
	    realloc(ps->coords_cache,
		    ps->numParticles * 4 * 2 * sizeof(GLfloat));
	ps->coords_cache_count = ps->numParticles;
    }

    if (ps->numParticles > ps->color_cache_count)
    {
	ps->colors_cache =
	    realloc(ps->colors_cache,
		    ps->numParticles * 4 * 4 * sizeof(GLfloat));
	ps->color_cache_count = ps->numParticles;
    }

    if (ps->darken > 0)
    {
	if (ps->dcolors_cache_count < ps->numParticles)
	{
	    ps->dcolors_cache =
		realloc(ps->dcolors_cache,
			ps->numParticles * 4 * 4 * sizeof(GLfloat));
	    ps->dcolors_cache_count = ps->numParticles;
	}
    }

    GLfloat *dcolors = ps->dcolors_cache;
    GLfloat *vertices = ps->vertices_cache;
    GLfloat *coords = ps->coords_cache;
    GLfloat *colors = ps->colors_cache;

    int numActive = 0;

    for (i = 0; i < ps->numParticles; i++)
    {
	part = &ps->particles[i];
	if (part->life > 0.0f)
	{
	    numActive += 4;

	    float w = part->width / 2;
	    float h = part->height / 2;

	    w += (w * part->w_mod) * part->life;
	    h += (h * part->h_mod) * part->life;

	    vertices[0] = part->x - w;
	    vertices[1] = part->y - h;
	    vertices[2] = part->z;

	    vertices[3] = part->x - w;
	    vertices[4] = part->y + h;
	    vertices[5] = part->z;

	    vertices[6] = part->x + w;
	    vertices[7] = part->y + h;
	    vertices[8] = part->z;

	    vertices[9] = part->x + w;
	    vertices[10] = part->y - h;
	    vertices[11] = part->z;

	    vertices += 12;

	    coords[0] = 0.0;
	    coords[1] = 0.0;

	    coords[2] = 0.0;
	    coords[3] = 1.0;

	    coords[4] = 1.0;
	    coords[5] = 1.0;

	    coords[6] = 1.0;
	    coords[7] = 0.0;

	    coords += 8;

	    colors[0] = part->r;
	    colors[1] = part->g;
	    colors[2] = part->b;
	    colors[3] = part->life * part->a;
	    colors[4] = part->r;
	    colors[5] = part->g;
	    colors[6] = part->b;
	    colors[7] = part->life * part->a;
	    colors[8] = part->r;
	    colors[9] = part->g;
	    colors[10] = part->b;
	    colors[11] = part->life * part->a;
	    colors[12] = part->r;
	    colors[13] = part->g;
	    colors[14] = part->b;
	    colors[15] = part->life * part->a;

	    colors += 16;

	    if (ps->darken > 0)
	    {

		dcolors[0] = part->r;
		dcolors[1] = part->g;
		dcolors[2] = part->b;
		dcolors[3] = part->life * part->a * ps->darken;
		dcolors[4] = part->r;
		dcolors[5] = part->g;
		dcolors[6] = part->b;
		dcolors[7] = part->life * part->a * ps->darken;
		dcolors[8] = part->r;
		dcolors[9] = part->g;
		dcolors[10] = part->b;
		dcolors[11] = part->life * part->a * ps->darken;
		dcolors[12] = part->r;
		dcolors[13] = part->g;
		dcolors[14] = part->b;
		dcolors[15] = part->life * part->a * ps->darken;

		dcolors += 16;
	    }
	}
    }

    glEnableClientState(GL_COLOR_ARRAY);

    glTexCoordPointer(2, GL_FLOAT, 2 * sizeof(GLfloat), ps->coords_cache);
    glVertexPointer(3, GL_FLOAT, 3 * sizeof(GLfloat), ps->vertices_cache);

    // darken the background
    if (ps->darken > 0)
    {
	glBlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_ALPHA);
	glColorPointer(4, GL_FLOAT, 4 * sizeof(GLfloat), ps->dcolors_cache);
	glDrawArrays(GL_QUADS, 0, numActive);
    }
    // draw particles
    glBlendFunc(GL_SRC_ALPHA, ps->blendMode);

    glColorPointer(4, GL_FLOAT, 4 * sizeof(GLfloat), ps->colors_cache);

    glDrawArrays(GL_QUADS, 0, numActive);

    glDisableClientState(GL_COLOR_ARRAY);

    glPopMatrix();
    glColor4usv(defaultColor);
    screenTexEnvMode(s, GL_REPLACE);
    glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
    glDisable(GL_TEXTURE_2D);
    glDisable(GL_BLEND);
}

void drawParticleSystems(CompScreen * s, CompWindow * w)
{
    ANIM_WINDOW(w);

    if (aw->numPs)
    {
	int i = 0;

	for (i = 0; i < aw->numPs; i++)
	{
	    if (aw->ps[i].active && !WINDOW_INVISIBLE(w))
	    {
		drawParticles(s, w, &aw->ps[i]);
	    }
	}
    }
}

void updateParticles(ParticleSystem * ps, float time)
{
    int i;
    Particle *part;
    float speed = (time / 50.0);
    float slowdown = ps->slowdown * (1 - MAX(0.99, time / 1000.0)) * 1000;

    ps->active = FALSE;

    for (i = 0; i < ps->numParticles; i++)
    {
	part = &ps->particles[i];
	if (part->life > 0.0f)
	{
	    // move particle
	    part->x += part->xi / slowdown;
	    part->y += part->yi / slowdown;
	    part->z += part->zi / slowdown;

	    // modify speed
	    part->xi += part->xg * speed;
	    part->yi += part->yg * speed;
	    part->zi += part->zg * speed;

	    // modify life
	    part->life -= part->fade * speed;
	    ps->active = TRUE;
	}
    }
}

void finiParticles(ParticleSystem * ps)
{
    free(ps->particles);
    if (ps->tex)
	glDeleteTextures(1, &ps->tex);

    if (ps->vertices_cache)
	free(ps->vertices_cache);
    if (ps->colors_cache)
	free(ps->colors_cache);
    if (ps->coords_cache)
	free(ps->coords_cache);
    if (ps->dcolors_cache)
	free(ps->dcolors_cache);
}

void
particlesUpdateBB (CompOutput *output,
		   CompWindow * w)
{
    ANIM_WINDOW(w);

    int i;
    for (i = 0; i < aw->numPs; i++)
    {
	ParticleSystem * ps = &aw->ps[i];
	if (ps->active)
	{
	    int j;
	    for (j = 0; j < ps->numParticles; j++)
	    {
		Particle *part = &ps->particles[j];

		float w = part->width / 2;
		float h = part->height / 2;

		w += (w * part->w_mod) * part->life;
		h += (h * part->h_mod) * part->life;

		Box particleBox =
		    {part->x - w, part->x + w,
		     part->y - h, part->y + h};

		expandBoxWithBox (&aw->BB, &particleBox);
	    }
	}
    }
    if (aw->useDrawRegion)
    {
	int nClip = aw->drawRegion->numRects;
	Box *pClip = aw->drawRegion->rects;

	for (; nClip--; pClip++)
	    expandBoxWithBox (&aw->BB, pClip);
    }
    else // drawing full window
	updateBBWindow (output, w);
}


syntax highlighted by Code2HTML, v. 0.9.1