/*
 * 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: Zoom and Sidekick  =========================

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

    // determine number of rotations randomly in [0.75, 1.25] range
    aw->numZoomRotations =
	animGetF(as, aw, ANIM_SCREEN_OPTION_SIDEKICK_NUM_ROTATIONS) *
	(1.0f + 0.2f * rand() / RAND_MAX - 0.1f);

    fxZoomInit(s, w);
}

static float
fxZoomGetSpringiness(AnimScreen *as, AnimWindow *aw)
{
    if (aw->curAnimEffect == AnimEffectZoom)
	return 2 * animGetF(as, aw, ANIM_SCREEN_OPTION_ZOOM_SPRINGINESS);
    else if (aw->curAnimEffect == AnimEffectSidekick)
	return 1.6 * animGetF(as, aw, ANIM_SCREEN_OPTION_SIDEKICK_SPRINGINESS);
    else
	return 0.0f;
}

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

    if ((aw->curAnimEffect == AnimEffectSidekick &&
	 (animGetI(as, aw, ANIM_SCREEN_OPTION_SIDEKICK_ZOOM_FROM_CENTER) ==
	  ZoomFromCenterOn ||
	  ((aw->curWindowEvent == WindowEventMinimize ||
	    aw->curWindowEvent == WindowEventUnminimize) &&
	   animGetI(as, aw, ANIM_SCREEN_OPTION_SIDEKICK_ZOOM_FROM_CENTER) ==
	   ZoomFromCenterMin) ||
	  ((aw->curWindowEvent == WindowEventOpen ||
	    aw->curWindowEvent == WindowEventClose) &&
	   animGetI(as, aw, ANIM_SCREEN_OPTION_SIDEKICK_ZOOM_FROM_CENTER) ==
	   ZoomFromCenterCreate))) ||
	(aw->curAnimEffect == AnimEffectZoom &&
	 (animGetI(as, aw, ANIM_SCREEN_OPTION_ZOOM_FROM_CENTER) ==
	  ZoomFromCenterOn ||
	  ((aw->curWindowEvent == WindowEventMinimize ||
	    aw->curWindowEvent == WindowEventUnminimize) &&
	   animGetI(as, aw, ANIM_SCREEN_OPTION_ZOOM_FROM_CENTER) ==
	   ZoomFromCenterMin) ||
	  ((aw->curWindowEvent == WindowEventOpen ||
	    aw->curWindowEvent == WindowEventClose) &&
	   animGetI(as, aw, ANIM_SCREEN_OPTION_ZOOM_FROM_CENTER) ==
	   ZoomFromCenterCreate))))
    {
	aw->icon.x =
	    WIN_X(w) + WIN_W(w) / 2 - aw->icon.width / 2;
	aw->icon.y =
	    WIN_Y(w) + WIN_H(w) / 2 - aw->icon.height / 2;
    }

    // allow extra time for spring damping / deceleration
    if ((aw->curWindowEvent == WindowEventUnminimize ||
	 aw->curWindowEvent == WindowEventOpen) &&
	fxZoomGetSpringiness(as, aw) > 1e-4)
    {
	aw->animTotalTime /= SPRINGY_ZOOM_PERCEIVED_T;
    }
    else if ((aw->curAnimEffect == AnimEffectZoom ||
	      aw->curAnimEffect == AnimEffectSidekick) &&
	     (aw->curWindowEvent == WindowEventOpen ||
	      aw->curWindowEvent == WindowEventClose))
    {
	aw->animTotalTime /= NONSPRINGY_ZOOM_PERCEIVED_T;
    }
    else
    {
	aw->animTotalTime /= ZOOM_PERCEIVED_T;
    }
    aw->animRemainingTime = aw->animTotalTime;

    defaultAnimInit(s, w);
}

void fxZoomAnimProgress(AnimScreen * as,
			AnimWindow * aw,
			float *moveProgress,
			float *scaleProgress,
			Bool neverSpringy)
{
    float forwardProgress =
	1 - aw->animRemainingTime /
	(aw->animTotalTime - aw->timestep);
    forwardProgress = MIN(forwardProgress, 1);
    forwardProgress = MAX(forwardProgress, 0);

    float x = forwardProgress;
    Bool backwards = FALSE;
    int animProgressDir = 1;

    if (aw->curWindowEvent == WindowEventUnminimize ||
	aw->curWindowEvent == WindowEventOpen)
	animProgressDir = 2;
    if (aw->animOverrideProgressDir != 0)
	animProgressDir = aw->animOverrideProgressDir;
    if ((animProgressDir == 1 &&
	 (aw->curWindowEvent == WindowEventUnminimize ||
	  aw->curWindowEvent == WindowEventOpen)) ||
	(animProgressDir == 2 &&
	 (aw->curWindowEvent == WindowEventMinimize ||
	  aw->curWindowEvent == WindowEventClose)))
	backwards = TRUE;
    if (backwards)
	x = 1 - x;

    float dampBase = (pow(1-pow(x,1.2)*0.5,10)-pow(0.5,10))/(1-pow(0.5,10));
    float nonSpringyProgress =
	1 - pow(decelerateProgressCustom(1 - x, .5f, .8f), 1.7f);

    if (moveProgress && scaleProgress)
    {
	float damping =
	    pow(dampBase, 0.5);

	float damping2 =
	    ((pow(1-(pow(x,0.7)*0.5),10)-pow(0.5,10))/(1-pow(0.5,10))) *
	    0.7 + 0.3;
	float springiness = 0;

	// springy only when appearing
	if ((aw->curWindowEvent == WindowEventUnminimize ||
	     aw->curWindowEvent == WindowEventOpen) &&
	    !neverSpringy)
	{
	    springiness = fxZoomGetSpringiness(as, aw);
	}
		
	float springyMoveProgress =
	    cos(2*M_PI*pow(x,1)*1.25) * damping * damping2;

	if (springiness > 1e-4f)
	{
	    if (x > 0.2)
	    {
		springyMoveProgress *= springiness;
	    }
	    else
	    {
		// interpolate between (springyMoveProgress * springiness)
		// and springyMoveProgress for smooth transition at 0.2
		// (where it crosses y=0)
		float progressUpto02 = x / 0.2f;
		springyMoveProgress =
		    (1 - progressUpto02) * springyMoveProgress +
		    progressUpto02 * springyMoveProgress * springiness;
	    }
	    *moveProgress = 1 - springyMoveProgress;
	}
	else
	{
	    *moveProgress = nonSpringyProgress;
	}
	if (aw->curWindowEvent == WindowEventUnminimize ||
	    aw->curWindowEvent == WindowEventOpen)
	    *moveProgress = 1 - *moveProgress;
	if (backwards)
	    *moveProgress = 1 - *moveProgress;

	float scProgress = nonSpringyProgress;
	if (aw->curWindowEvent == WindowEventUnminimize ||
	    aw->curWindowEvent == WindowEventOpen)
	    scProgress = 1 - scProgress;
	if (backwards)
	    scProgress = 1 - scProgress;

	*scaleProgress =
	    pow(scProgress, 1.25);
    }
}

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

    float forwardProgress;
    float dummy;

    fxZoomAnimProgress(as, aw, &dummy, &forwardProgress, FALSE);

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

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

    Point winCenter =
	{(WIN_X(w) + WIN_W(w) / 2.0),
	 (WIN_Y(w) + WIN_H(w) / 2.0)};
    Point iconCenter =
	{aw->icon.x + aw->icon.width / 2.0,
	 aw->icon.y + aw->icon.height / 2.0};
    Point winSize =
	{WIN_W(w), WIN_H(w)};
    winSize.x = (winSize.x == 0 ? 1 : winSize.x);
    winSize.y = (winSize.y == 0 ? 1 : winSize.y);

    float scaleProgress;
    float moveProgress;
    float rotateProgress = 0;

    if (aw->curAnimEffect == AnimEffectSidekick)
    {
	fxZoomAnimProgress(as, aw, &moveProgress, &scaleProgress, FALSE);
	rotateProgress = moveProgress;
    }
    else if (aw->curAnimEffect == AnimEffectZoom)
    {
	fxZoomAnimProgress(as, aw, &moveProgress, &scaleProgress, FALSE);
    }
    else
    {
	// other effects use this for minimization
	fxZoomAnimProgress(as, aw, &moveProgress, &scaleProgress, TRUE);
    }

    Point curCenter =
	{(1 - moveProgress) * winCenter.x + moveProgress * iconCenter.x,
	 (1 - moveProgress) * winCenter.y + moveProgress * iconCenter.y};
    Point curScale =
	{((1 - scaleProgress) * winSize.x + scaleProgress * aw->icon.width) /
	 winSize.x,
	 ((1 - scaleProgress) * winSize.y + scaleProgress * aw->icon.height) /
	 winSize.y};

    if (fxZoomGetSpringiness(as, aw) == 0.0f &&
	(aw->curAnimEffect == AnimEffectZoom ||
	 aw->curAnimEffect == AnimEffectSidekick) &&
	(aw->curWindowEvent == WindowEventOpen ||
	 aw->curWindowEvent == WindowEventClose))
    {
	matrixTranslate (transform,
			 iconCenter.x, iconCenter.y, 0);
	matrixScale (transform, curScale.x, curScale.y, 1.0f);
	matrixTranslate (transform,
			 -iconCenter.x, -iconCenter.y, 0);

	if (aw->curAnimEffect == AnimEffectSidekick)
	{
	    matrixTranslate (transform, winCenter.x, winCenter.y, 0);
	    matrixRotate (transform, rotateProgress * 360 * aw->numZoomRotations,
			  0.0f, 0.0f, 1.0f);
	    matrixTranslate (transform, -winCenter.x, -winCenter.y, 0);
	}
    }
    else
    {
	matrixTranslate (transform, winCenter.x, winCenter.y, 0);
	float tx, ty;
	if (aw->curAnimEffect != AnimEffectZoom)
	{
	    // avoid parallelogram look
	    float maxScale = MAX(curScale.x, curScale.y);
	    matrixScale (transform, maxScale, maxScale, 1.0f);
	    tx = (curCenter.x - winCenter.x) / maxScale;
	    ty = (curCenter.y - winCenter.y) / maxScale;
	}
	else
	{
	    matrixScale (transform, curScale.x, curScale.y, 1.0f);
	    tx = (curCenter.x - winCenter.x) / curScale.x;
	    ty = (curCenter.y - winCenter.y) / curScale.y;
	}
	matrixTranslate (transform, tx, ty, 0);
	if (aw->curAnimEffect == AnimEffectSidekick)
	{
	    matrixRotate (transform, rotateProgress * 360 * aw->numZoomRotations,
			  0.0f, 0.0f, 1.0f);
	}
	matrixTranslate (transform, -winCenter.x, -winCenter.y, 0);
    }
}

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

    // Apply transform to wTransform
    matmul4 (wTransform->m, wTransform->m, aw->transform.m);
}


syntax highlighted by Code2HTML, v. 0.9.1