/*
 * 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"

// =====================  Effect: Glide  =========================

static void
fxGlideGetParams (AnimScreen *as,
		  AnimWindow *aw,
		  float *finalDistFac,
		  float *finalRotAng,
		  float *thickness)
{
    if (aw->curAnimEffect == AnimEffectGlide3D1)
    {
	*finalDistFac = animGetF(as, aw, ANIM_SCREEN_OPTION_GLIDE1_AWAY_POS);
	*finalRotAng = animGetF(as, aw, ANIM_SCREEN_OPTION_GLIDE1_AWAY_ANGLE);
	*thickness = animGetF(as, aw, ANIM_SCREEN_OPTION_GLIDE1_THICKNESS);
    }
    else
    {
	*finalDistFac = animGetF(as, aw, ANIM_SCREEN_OPTION_GLIDE2_AWAY_POS);
	*finalRotAng = animGetF(as, aw, ANIM_SCREEN_OPTION_GLIDE2_AWAY_ANGLE);
	*thickness = animGetF(as, aw, ANIM_SCREEN_OPTION_GLIDE2_THICKNESS);
    }
}

Bool
fxGlideIsPolygonBased (AnimScreen *as, AnimWindow *aw)
{
    if (aw->curAnimEffect == AnimEffectGlide3D1)
	return (animGetF(as, aw, ANIM_SCREEN_OPTION_GLIDE1_THICKNESS) > 1e-5);
    else
	return (animGetF(as, aw, ANIM_SCREEN_OPTION_GLIDE2_THICKNESS) > 1e-5);
}

static inline Bool
fxGlideZoomToTaskBar (AnimScreen *as, AnimWindow *aw)
{
    return
	(aw->curWindowEvent == WindowEventMinimize ||
	 aw->curWindowEvent == WindowEventUnminimize) &&
	((aw->curAnimEffect == AnimEffectGlide3D1 &&
	  animGetB(as, aw, ANIM_SCREEN_OPTION_GLIDE1_Z2TOM)) ||
	 (aw->curAnimEffect == AnimEffectGlide3D2 &&
	  animGetB(as, aw, ANIM_SCREEN_OPTION_GLIDE2_Z2TOM)));
}

Bool
fxGlideLetOthersDrawGeoms(CompScreen *s, CompWindow *w)
{
    ANIM_SCREEN(s);
    ANIM_WINDOW(w);

    return !fxGlideIsPolygonBased(as, aw);
}

float fxGlideAnimProgress(AnimWindow * aw)
{
    float forwardProgress = defaultAnimProgress(aw);

    return decelerateProgress(forwardProgress);
}

// Scales z by 0 and does perspective distortion so that it
// looks the same wherever on screen
static void
resetAndPerspectiveDistortOnZ (CompTransform *wTransform, float v)
{
    /*
      This does
      wTransform = M * wTransform, where M is
      1, 0, 0, 0,
      0, 1, 0, 0,
      0, 0, 0, v,
      0, 0, 0, 1
    */
    float *m = wTransform->m;
    m[8] = v * m[12];
    m[9] = v * m[13];
    m[10] = v * m[14];
    m[11] = v * m[15];
}

static void
applyGlideTransform (CompWindow *w, CompTransform *transform)
{
    ANIM_SCREEN(w->screen);
    ANIM_WINDOW(w);

    float finalDistFac;
    float finalRotAng;
    float thickness;

    fxGlideGetParams(as, aw, &finalDistFac, &finalRotAng, &thickness);

    float forwardProgress;
    if (fxGlideZoomToTaskBar(as, aw))
    {
	float dummy;
	fxZoomAnimProgress(as, aw, &forwardProgress, &dummy, TRUE);
    }
    else
	forwardProgress = fxGlideAnimProgress(aw);

    float finalz = finalDistFac * 0.8 * DEFAULT_Z_CAMERA *
	w->screen->width;

    Vector3d rotAxis = {1, 0, 0};
    Point3d rotAxisOffset =
	{WIN_X(w) + WIN_W(w) / 2.0f,
	 WIN_Y(w) + WIN_H(w) / 2.0f,
	 0};
    Point3d translation = {0, 0, finalz * forwardProgress};

    float rotAngle = finalRotAng * forwardProgress;
    aw->glideModRotAngle = fmodf(rotAngle + 720, 360.0f);

    // put back to window position
    matrixTranslate (transform, rotAxisOffset.x, rotAxisOffset.y, 0);

    resetAndPerspectiveDistortOnZ (transform, -1.0 / w->screen->width);

    // animation movement
    matrixTranslate (transform, translation.x, translation.y, translation.z);

    // animation rotation
    matrixRotate (transform, rotAngle, rotAxis.x, rotAxis.y, rotAxis.z);

    // intentional scaling of z by 0 to prevent weird opacity results and
    // flashing that happen when z coords are between 0 and 1 (bug in compiz?)
    matrixScale (transform, 1.0f, 1.0f, 0.0f);

    // place window rotation axis at origin
    matrixTranslate (transform, -rotAxisOffset.x, -rotAxisOffset.y, 0);
}

Bool fxGlideAnimStep(CompScreen * s, CompWindow * w, float time)
{
    ANIM_SCREEN(s);
    ANIM_WINDOW(w);

    if (fxGlideIsPolygonBased(as, aw))
	return polygonsAnimStep(s, w, time);
    else
    {
	if (!defaultAnimStep(s, w, time))
	    return FALSE;

	applyGlideTransform (w, &aw->transform);

	return TRUE;
    }
}

void
fxGlideUpdateWindowAttrib(AnimScreen * as,
			  CompWindow * w,
			  WindowPaintAttrib * wAttrib)
{
    ANIM_WINDOW(w);

    if (fxGlideIsPolygonBased(as, aw))
	return;

    // the effect is CompTransform-based

    if (fxGlideZoomToTaskBar(as, aw))
    {
	fxZoomUpdateWindowAttrib(as, w, wAttrib);
	return;
    }

    float forwardProgress = fxGlideAnimProgress(aw);

    wAttrib->opacity = aw->storedOpacity * (1 - forwardProgress);
}

void
fxGlideUpdateWindowTransform (CompScreen *s,
			      CompWindow *w,
			      CompTransform *wTransform)
{
    ANIM_SCREEN(s);
    ANIM_WINDOW(w);

    if (fxGlideIsPolygonBased (as, aw))
	return;

    // apply the transform
    matmul4 (wTransform->m, wTransform->m, aw->transform.m);
}

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

    if (fxGlideZoomToTaskBar(as, aw))
    {
	aw->animTotalTime /= ZOOM_PERCEIVED_T;
	aw->animRemainingTime = aw->animTotalTime;
    }

    if (!fxGlideIsPolygonBased(as, aw))
    {
	defaultAnimInit (s, w);
	return; // we're done with CompTransform-based glide initialization
    }

    // for polygon-based glide effect

    float finalDistFac;
    float finalRotAng;
    float thickness;

    fxGlideGetParams(as, aw, &finalDistFac, &finalRotAng, &thickness);

    PolygonSet *pset = aw->polygonSet;

    pset->includeShadows = (thickness < 1e-5);

    if (!tessellateIntoRectangles(w, 1, 1, thickness))
	return;

    PolygonObject *p = pset->polygons;

    int i;

    for (i = 0; i < pset->nPolygons; i++, p++)
    {
	p->rotAxis.x = 1;
	p->rotAxis.y = 0;
	p->rotAxis.z = 0;

	p->finalRelPos.x = 0;
	p->finalRelPos.y = 0;
	p->finalRelPos.z = finalDistFac * 0.8 * DEFAULT_Z_CAMERA * s->width;

	p->finalRotAng = finalRotAng;
    }
    pset->allFadeDuration = 1.0f;
    pset->backAndSidesFadeDur = 0.2f;
    pset->doLighting = TRUE;
    pset->correctPerspective = CorrectPerspectivePolygon;
}

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

    if (fxGlideIsPolygonBased(as, aw))
	polygonsPrePaintWindow(s, w);
    else if (90 < aw->glideModRotAngle &&
	     aw->glideModRotAngle < 270)
	glCullFace(GL_FRONT);
}

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

    if (fxGlideIsPolygonBased(as, aw))
	polygonsPostPaintWindow(s, w);
    else if (90 < aw->glideModRotAngle &&
	     aw->glideModRotAngle < 270)
	glCullFace(GL_BACK);
}

void
fxGlideUpdateBB (CompOutput *output,
		 CompWindow *w)
{
    ANIM_SCREEN(w->screen);
    ANIM_WINDOW(w);

    if (fxGlideIsPolygonBased (as, aw))
	polygonsUpdateBB (output, w);
    else
	compTransformUpdateBB (output, w);
}


syntax highlighted by Code2HTML, v. 0.9.1