/**
 * 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>
 *
 * Airplane added by : Carlo Palma
 * E-mail            : carlopalma@salug.it
 * Based on code originally written by Mark J. Kilgard
 *
 * Beam-Up added by : Florencio Guimaraes
 * E-mail           : florencio@nexcorp.com.br
 *
 * Fold and Skewer added by : Tomasz Kolodziejski
 * E-mail                   : tkolodziejski@gmail.com
 *
 * Hexagon tessellator added by : Mike Slegeir
 * E-mail                       : mikeslegeir@mail.utexas.edu>
 *
 * Particle system added by : (C) 2006 Dennis Kasprzyk
 * E-mail                   : onestone@beryl-project.org
 *
 * 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.
 **/

/*
 * TODO:
 *
 * - Custom bounding box update function for Airplane
 *
 * - Auto direction option: Close in opposite direction of opening
 * - Proper side surface normals for lighting
 * - decoration shadows
 *   - shadow quad generation
 *   - shadow texture coords (from clip tex. matrices)
 *   - draw shadows
 *   - fade in shadows
 *
 * - Voronoi tessellation
 * - Brick tessellation
 * - Triangle tessellation
 * - Hexagonal tessellation
 *
 * Effects:
 * - Circular action for tornado type fx
 * - Tornado 3D (especially for minimize)
 * - Helix 3D (hor. strips descend while they rotate and fade in)
 * - Glass breaking 3D
 *   - Gaussian distr. points (for gradually increasing polygon size
 *                           starting from center or near mouse pointer)
 *   - Drawing cracks
 *   - Gradual cracking
 *
 * - fix slowness during transparent cube with <100 opacity
 * - fix occasional wrong side color in some windows
 * - fix on top windows and panels
 *   (These two only matter for viewing during Rotate Cube.
 *    All windows should be painted with depth test on
 *    like 3d-plugin does)
 * - play better with rotate (fix cube face drawn on top of polygons
 *   after 45 deg. rotation)
 *
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <limits.h>
#include <GL/glu.h>
#include "animation-internal.h"

int animDisplayPrivateIndex;
CompMetadata animMetadata;

/*
 * NOTE:
 * Be sure to always update the NUM_EFFECT_TYPE
 * macro definitions in animation-internal.h whenever
 * elements of these arrays are added or removed.
 */

static AnimEffect minimizeEffects[] = {
    AnimEffectNone,
    AnimEffectRandom,
    AnimEffectAirplane3D,
    AnimEffectBeamUp,
    AnimEffectBurn,
    AnimEffectCurvedFold,
    AnimEffectDomino3D,
    AnimEffectDream,
    AnimEffectExplode3D,
    AnimEffectFade,
    AnimEffectFold3D,
    AnimEffectGlide3D1,
    AnimEffectGlide3D2,
    AnimEffectHorizontalFolds,
    AnimEffectLeafSpread3D,
    AnimEffectMagicLamp,
    AnimEffectRazr3D,
    AnimEffectSidekick,
    AnimEffectSkewer,
    AnimEffectZoom
};

static AnimEffect closeEffects[] = {
    AnimEffectNone,
    AnimEffectRandom,
    AnimEffectAirplane3D,
    AnimEffectBeamUp,
    AnimEffectBurn,
    AnimEffectCurvedFold,
    AnimEffectDomino3D,
    AnimEffectDream,
    AnimEffectExplode3D,
    AnimEffectFade,
    AnimEffectFold3D,
    AnimEffectGlide3D1,
    AnimEffectGlide3D2,
    AnimEffectHorizontalFolds,
    AnimEffectLeafSpread3D,
    AnimEffectMagicLamp,
    AnimEffectRazr3D,
    AnimEffectSidekick,
    AnimEffectSkewer,
    AnimEffectVacuum,
    AnimEffectWave,
    AnimEffectZoom
};

static AnimEffect focusEffects[] = {
    AnimEffectNone,
    AnimEffectDodge,
    AnimEffectFocusFade,
    AnimEffectWave
};

static AnimEffect shadeEffects[] = {
    AnimEffectNone,
    AnimEffectRandom,
    AnimEffectCurvedFold,
    AnimEffectHorizontalFolds,
    AnimEffectRollUp
};

float identity[16] = {
    1.0, 0.0, 0.0, 0.0,
    0.0, 1.0, 0.0, 0.0,
    0.0, 0.0, 1.0, 0.0,
    0.0, 0.0, 0.0, 1.0
};

static int switcherPostWait = 0;


// Remove when matmul4 in matrix.c made publicly accessible

#define A(row, col) a[(col << 2) + row]
#define B(row, col) b[(col << 2) + row]
#define P(row, col) product[(col << 2) + row]

/**
 * Perform a full 4x4 matrix multiplication.
 *
 * \param a matrix.
 * \param b matrix.
 * \param product will receive the product of \p a and \p b.
 *
 * \warning Is assumed that \p product != \p b. \p product == \p a is allowed.
 *
 * \note KW: 4*16 = 64 multiplications
 *
 * \author This \c matmul was contributed by Thomas Malik
 */
void
matmul4 (float       *product,
	 const float *a,
	 const float *b)
{
    int i;

    for (i = 0; i < 4; i++)
    {
	const float ai0 = A(i,0), ai1 = A(i,1), ai2 = A(i,2), ai3 = A(i,3);

	P(i,0) = ai0 * B(0,0) + ai1 * B(1,0) + ai2 * B(2,0) + ai3 * B(3,0);
	P(i,1) = ai0 * B(0,1) + ai1 * B(1,1) + ai2 * B(2,1) + ai3 * B(3,1);
	P(i,2) = ai0 * B(0,2) + ai1 * B(1,2) + ai2 * B(2,2) + ai3 * B(3,2);
	P(i,3) = ai0 * B(0,3) + ai1 * B(1,3) + ai2 * B(2,3) + ai3 * B(3,3);
    }
}

#undef A
#undef B
#undef P


// Perform multiplication of 4x4 matrix by a vector
void
multiplyMatrixVector (float *result,
		      const float *mat,
		      const float *v)
{
#define M(row, col) mat[(col << 2) + row]
    int i;
    for (i = 0; i < 4; i++)
	result[i] = M(i,0) * v[0] + M(i,1) * v[1] + M(i,2) * v[2] + M(i,3) * v[3];
#undef M
}

// iterate over given list
// check if given effect name matches any implemented effect
// Check if it was already in the stored list
// if not, store the effect
// if no valid effect is given, use the default effect

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

    // store window opacity
    aw->storedOpacity = w->paint.opacity;

    aw->timestep = (s->slowAnimations ? 2 :	// For smooth slow-mo (refer to display.c)
		    as->opt[ANIM_SCREEN_OPTION_TIME_STEP].value.i);
}

Bool
animZoomToIcon (AnimScreen *as, AnimWindow *aw)
{
    return
	aw->curAnimEffect == AnimEffectZoom ||
	aw->curAnimEffect == AnimEffectSidekick ||
	((aw->curWindowEvent == WindowEventMinimize ||
	  aw->curWindowEvent == WindowEventUnminimize) &&
	 ((aw->curAnimEffect == AnimEffectCurvedFold &&
	   animGetB(as, aw, ANIM_SCREEN_OPTION_CURVED_FOLD_Z2TOM)) ||
	  (aw->curAnimEffect == AnimEffectDream &&
	   animGetB(as, aw, ANIM_SCREEN_OPTION_DREAM_Z2TOM)) ||
	  (aw->curAnimEffect == AnimEffectGlide3D1 &&
	   animGetB(as, aw, ANIM_SCREEN_OPTION_GLIDE1_Z2TOM)) ||
	  (aw->curAnimEffect == AnimEffectGlide3D2 &&
	   animGetB(as, aw, ANIM_SCREEN_OPTION_GLIDE2_Z2TOM)) ||
	  (aw->curAnimEffect == AnimEffectHorizontalFolds &&
	   animGetB(as, aw, ANIM_SCREEN_OPTION_HORIZONTAL_FOLDS_Z2TOM))));
}

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

    if (animZoomToIcon(as, aw))
    {
	aw->animTotalTime /= ZOOM_PERCEIVED_T;
	aw->animRemainingTime = aw->animTotalTime;
    }
    defaultAnimInit(s, w);
}

static inline Bool
defaultLetOthersDrawGeoms (CompScreen *s, CompWindow *w)
{
    return TRUE;
}

static void
animStoreRandomEffectList (CompOptionValue *value,
			   AnimEffect *allowedEffects,
			   unsigned int numAllowedEffects,
			   AnimEffect *targetList,
			   unsigned int *targetCount)
{
    CompOptionValue *effect = value->list.value;
    AnimEffect listEffect;
    int nItems = value->list.nValue;
    int i, j, count;

    count = 0;

    for (i = 0; i < nItems; i++, effect++)
    {
	if ((effect->i < 0) || (effect->i >= numAllowedEffects))
	    continue;

	listEffect = allowedEffects[effect->i];

	for (j = 0; j < count; j++)
	{
	    if (targetList[j] == listEffect)
		break;
	}

	if (j < count)
	    continue;

	targetList[count] = listEffect;
	count++;
    }

    *targetCount = count;
}

// Assumes events in the metadata are in
// [Open, Close, Minimize, Focus, Shade] order
// and effects among those are in alphabetical order
// but with "(Event) None" first and "(Event) Random" last.
static AnimEffect
getMatchingAnimSelection (CompWindow *w,
			  WindowEvent event,
			  int *duration)
{
    ANIM_SCREEN(w->screen);
    ANIM_WINDOW(w);

    if (duration == NULL)
	return AnimEffectNone;

    CompOptionValue *valMatch;
    CompOptionValue *valEffect;
    CompOptionValue *valDuration;
    AnimEffect *effects;

    switch (event)
    {
    case WindowEventOpen:
	effects = closeEffects;
	valMatch = &as->opt[ANIM_SCREEN_OPTION_OPEN_MATCHES].value;
	valEffect = &as->opt[ANIM_SCREEN_OPTION_OPEN_EFFECTS].value;
	valDuration = &as->opt[ANIM_SCREEN_OPTION_OPEN_DURATIONS].value;
	break;
    case WindowEventClose:
	effects = closeEffects;
	valMatch = &as->opt[ANIM_SCREEN_OPTION_CLOSE_MATCHES].value;
	valEffect = &as->opt[ANIM_SCREEN_OPTION_CLOSE_EFFECTS].value;
	valDuration = &as->opt[ANIM_SCREEN_OPTION_CLOSE_DURATIONS].value;
	break;
    case WindowEventMinimize:
    case WindowEventUnminimize:
	effects = minimizeEffects;
	valMatch = &as->opt[ANIM_SCREEN_OPTION_MINIMIZE_MATCHES].value;
	valEffect = &as->opt[ANIM_SCREEN_OPTION_MINIMIZE_EFFECTS].value;
	valDuration = &as->opt[ANIM_SCREEN_OPTION_MINIMIZE_DURATIONS].value;
	break;
    case WindowEventFocus:
	effects = focusEffects;
	valMatch = &as->opt[ANIM_SCREEN_OPTION_FOCUS_MATCHES].value;
	valEffect = &as->opt[ANIM_SCREEN_OPTION_FOCUS_EFFECTS].value;
	valDuration = &as->opt[ANIM_SCREEN_OPTION_FOCUS_DURATIONS].value;
	break;
    case WindowEventShade:
    case WindowEventUnshade:
	effects = shadeEffects;
	valMatch = &as->opt[ANIM_SCREEN_OPTION_SHADE_MATCHES].value;
	valEffect = &as->opt[ANIM_SCREEN_OPTION_SHADE_EFFECTS].value;
	valDuration = &as->opt[ANIM_SCREEN_OPTION_SHADE_DURATIONS].value;
	break;
    case WindowEventNone:
    default:
	return AnimEffectNone;
    }

    int nRows = valMatch->list.nValue;
    if (nRows != valEffect->list.nValue ||
	nRows != valDuration->list.nValue)
    {
	compLogMessage
	    (w->screen->display, "animation", CompLogLevelError,
	     "Number of animation selection effects, durations, matches, and options are not the same.");
	return AnimEffectNone;
    }

    // Find the first row that matches this window for this event
    int i;
    for (i = 0; i < nRows; i++)
    {
	if (!matchEval (&valMatch->list.value[i].match, w))
	    continue;

	aw->curAnimSelectionRow = i;

	*duration = valDuration->list.value[i].i;

	return effects[valEffect->list.value[i].i];
    }
    return AnimEffectNone;
}

static inline AnimEffect
animGetAnimEffect (AnimEffect effect,
		   AnimEffect *randomEffects,
		   unsigned int nRandomEffects,
		   Bool allRandom)
{
    if ((effect == AnimEffectRandom) || allRandom)
    {
	if (nRandomEffects == 0)
	    return AnimEffectNone;
	else
	{
	    unsigned int index;
	    index = (unsigned int)(nRandomEffects * (double)rand() / RAND_MAX);
	    return randomEffects[index];
	}
    }
    else
	return effect;
}

// Converts animation direction string to an integer direction
// (up, down, left, or right)
AnimDirection getAnimationDirection(CompWindow * w,
				    CompOptionValue *value, 
				    Bool openDir)
{
    ANIM_WINDOW(w);

    AnimDirection dir;

    dir = value->i;

    if (dir == AnimDirectionRandom)
    {
	dir = rand() % 4;
    }
    else if (dir == AnimDirectionAuto)
    {
	// away from icon
	int centerX = BORDER_X(w) + BORDER_W(w) / 2;
	int centerY = BORDER_Y(w) + BORDER_H(w) / 2;
	float relDiffX = ((float)centerX - aw->icon.x) / BORDER_W(w);
	float relDiffY = ((float)centerY - aw->icon.y) / BORDER_H(w);

	if (openDir)
	{
	    if (aw->curWindowEvent == WindowEventMinimize ||
		aw->curWindowEvent == WindowEventUnminimize)
		// min/unmin. should always result in +/- y direction
		dir = aw->icon.y < w->screen->height - aw->icon.y ?
		    AnimDirectionDown : AnimDirectionUp;
	    else if (fabs(relDiffY) > fabs(relDiffX))
		dir = relDiffY > 0 ? AnimDirectionDown : AnimDirectionUp;
	    else
		dir = relDiffX > 0 ? AnimDirectionRight : AnimDirectionLeft;
	}
	else
	{
	    if (aw->curWindowEvent == WindowEventMinimize ||
		aw->curWindowEvent == WindowEventUnminimize)
		// min/unmin. should always result in +/- y direction
		dir = aw->icon.y < w->screen->height - aw->icon.y ?
		    AnimDirectionUp : AnimDirectionDown;
	    else if (fabs(relDiffY) > fabs(relDiffX))
		dir = relDiffY > 0 ? AnimDirectionUp : AnimDirectionDown;
	    else
		dir = relDiffX > 0 ? AnimDirectionLeft : AnimDirectionRight;
	}
    }
    return dir;
}

float defaultAnimProgress(AnimWindow * aw)
{
    float forwardProgress =
	1 - aw->animRemainingTime / (aw->animTotalTime - aw->timestep);
    forwardProgress = MIN(forwardProgress, 1);
    forwardProgress = MAX(forwardProgress, 0);

    if (aw->curWindowEvent == WindowEventOpen ||
	aw->curWindowEvent == WindowEventUnminimize ||
	aw->curWindowEvent == WindowEventUnshade ||
	aw->curWindowEvent == WindowEventFocus)
	forwardProgress = 1 - forwardProgress;

    return forwardProgress;
}

float sigmoidAnimProgress(AnimWindow * aw)
{
    float forwardProgress =
	1 - aw->animRemainingTime / (aw->animTotalTime - aw->timestep);
    forwardProgress = MIN(forwardProgress, 1);
    forwardProgress = MAX(forwardProgress, 0);

    // Apply sigmoid and normalize
    forwardProgress =
	(sigmoid(forwardProgress) - sigmoid(0)) /
	(sigmoid(1) - sigmoid(0));

    if (aw->curWindowEvent == WindowEventOpen ||
	aw->curWindowEvent == WindowEventUnminimize ||
	aw->curWindowEvent == WindowEventUnshade ||
	aw->curWindowEvent == WindowEventFocus)
	forwardProgress = 1 - forwardProgress;

    return forwardProgress;
}

// Gives some acceleration (when closing a window)
// or deceleration (when opening a window)
// Applies a sigmoid with slope s,
// where minx and maxx are the
// starting and ending points on the sigmoid
float decelerateProgressCustom(float progress, float minx, float maxx)
{
    float x = 1 - progress;
    float s = 8;

    return (1 -
	    ((sigmoid2(minx + (x * (maxx - minx)), s) - sigmoid2(minx, s)) /
	     (sigmoid2(maxx, s) - sigmoid2(minx, s))));
}

float decelerateProgress(float progress)
{
    return decelerateProgressCustom(progress, 0.5, 0.75);
}

Bool defaultAnimStep(CompScreen * s, CompWindow * w, float time)
{
    int steps;

    ANIM_WINDOW(w);
    ANIM_SCREEN(s);

    float timestep = (s->slowAnimations ? 2 :	// For smooth slow-mo (refer to display.c)
		      as->opt[ANIM_SCREEN_OPTION_TIME_STEP].value.i);

    aw->timestep = timestep;

    aw->remainderSteps += time / timestep;
    steps = floor(aw->remainderSteps);
    aw->remainderSteps -= steps;

    if (!steps && aw->animRemainingTime < aw->animTotalTime)
	return FALSE;
    steps = MAX(1, steps);

    aw->animRemainingTime -= timestep * steps;

    // avoid sub-zero values
    aw->animRemainingTime = MAX(aw->animRemainingTime, 0);

    resetToIdentity (&aw->transform);
    if (animZoomToIcon(as, aw))
    {
	applyZoomTransform (w, &aw->transform);
    }
    return TRUE;
}

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

    if (animZoomToIcon(as, aw))
	fxZoomUpdateWindowAttrib(as, w, wAttrib);
}

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

    if (animZoomToIcon(as, aw))
	fxZoomUpdateWindowTransform(s, w, wTransform);
}

static void
copyResetBB (AnimWindow *aw)
{
    memcpy (&aw->lastBB, &aw->BB, sizeof (Box));
    aw->BB.x1 = aw->BB.y1 = MAXSHORT;
    aw->BB.x2 = aw->BB.y2 = MINSHORT;
}

void
expandBoxWithBox (Box *target, Box *source)
{
    if (source->x1 < target->x1)
	target->x1 = source->x1;
    if (source->x2 > target->x2)
	target->x2 = source->x2;
    if (source->y1 < target->y1)
	target->y1 = source->y1;
    if (source->y2 > target->y2)
	target->y2 = source->y2;
}

void
expandBoxWithPoint (Box *target, float fx, float fy)
{
    short x = MAX (MIN (fx, MAXSHORT - 1), MINSHORT);
    short y = MAX (MIN (fy, MAXSHORT - 1), MINSHORT);

    if (target->x1 == MAXSHORT)
    {
	target->x1 = x;
	target->y1 = y;
	target->x2 = x + 1;
	target->y2 = y + 1;
	return;
    }
    if (x < target->x1)
	target->x1 = x;
    else if (x > target->x2)
	target->x2 = x;

    if (y < target->y1)
	target->y1 = y;
    else if (y > target->y2)
	target->y2 = y;
}

// This will work for zoom-like 2D transforms,
// but not for glide-like 3D transforms.
static void
expandBoxWithPoint2DTransform (CompScreen *s,
			       Box *target,
			       float x,
			       float y,
			       float *transformMat)
{
    float coords[4] = {x, y, 0, 1};
    float coordsTransformed[4];
    multiplyMatrixVector (coordsTransformed, transformMat, coords);

    expandBoxWithPoint (target, coordsTransformed[0], coordsTransformed[1]);
}

static Bool
expandBoxWithPoints3DTransform (CompOutput          *output,
				CompScreen          *s,
				const CompTransform *transform,
				Box                 *targetBox,
				const float         *points,
				int                 nPoints)
{
    GLdouble dModel[16];
    GLdouble dProjection[16];
    GLdouble x, y, z;
    int i;
    for (i = 0; i < 16; i++)
    {
	dModel[i] = transform->m[i];
	dProjection[i] = s->projection[i];
    }
    GLint viewport[4] =
	{output->region.extents.x1,
	 output->region.extents.y1,
	 output->width,
	 output->height};

    while (nPoints--)
    {
	if (!gluProject (points[0], points[1], points[2],
			 dModel, dProjection, viewport,
			 &x, &y, &z))
	    return FALSE;

	expandBoxWithPoint (targetBox, x + 0.5, (s->height - y) + 0.5);

	points += 3;
    }

    return TRUE;
}

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

    Model *model = aw->model;
    if (!model)
	return;

    float x, y;
    int i;
    if (animZoomToIcon(as, aw))
	for (i = 0; i < model->numObjects; i++)
	{
	    x = model->objects[i].position.x;
	    y = model->objects[i].position.y;

	    expandBoxWithPoint2DTransform (w->screen,
					   &aw->BB,
					   x, y,
					   aw->transform.m);
	}
    else
	for (i = 0; i < model->numObjects; i++)
	{
	    x = model->objects[i].position.x + 0.5;
	    y = model->objects[i].position.y + 0.5;

	    expandBoxWithPoint (&aw->BB, x, y);
	}
}

void
updateBBWindow (CompOutput *output,
		CompWindow * w)
{
    ANIM_WINDOW(w);
    Box windowBox = {WIN_X(w), WIN_X(w) + WIN_W(w),
		     WIN_Y(w), WIN_Y(w) + WIN_H(w)};
    expandBoxWithBox (&aw->BB, &windowBox);
}

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

    Box screenBox = {0, w->screen->width,
		     0, w->screen->height};
    expandBoxWithBox (&aw->BB, &screenBox);
}

inline void
resetToIdentity (CompTransform *transform)
{
    memcpy (transform->m, identity, 16 * sizeof(float));
}

void
prepareTransform (CompScreen *s,
		  CompOutput *output,
		  CompTransform *resultTransform,
		  CompTransform *transform)
{
    CompTransform sTransform;

    resetToIdentity (&sTransform);

    transformToScreenSpace (s, output,
			    -DEFAULT_Z_CAMERA, &sTransform);

    matmul4 (resultTransform->m, sTransform.m, transform->m);
}

void
compTransformUpdateBB (CompOutput *output,
		       CompWindow *w)
{
    ANIM_WINDOW(w);
    CompScreen *s = w->screen;
    CompTransform wTransform;

    prepareTransform (s, output, &wTransform, &aw->transform);

    float corners[4*3] = {WIN_X(w), WIN_Y(w), 0,
			  WIN_X(w) + WIN_W(w), WIN_Y(w), 0,
			  WIN_X(w), WIN_Y(w) + WIN_H(w), 0,
			  WIN_X(w) + WIN_W(w), WIN_Y(w) + WIN_H(w), 0};

    expandBoxWithPoints3DTransform (output,
				    s,
				    &wTransform,
				    &aw->BB,
				    corners,
				    4);
}

// Damage the union of window's bounding box
// before and after animStepFunc does its job
static void
damageBoundingBox (CompWindow * w)
{
    ANIM_WINDOW(w);

    if (aw->BB.x1 == MAXSHORT) // unintialized BB
	return;

    // Find union of BB and lastBB
    Box box;
    box.x1 = MIN (aw->BB.x1, aw->lastBB.x1) - 1;
    box.y1 = MIN (aw->BB.y1, aw->lastBB.y1) - 1;
    box.x2 = MAX (aw->BB.x2, aw->lastBB.x2) + 1;
    box.y2 = MAX (aw->BB.y2, aw->lastBB.y2) + 1;
    // prevent occasional 1 pixel line artifact

    box.x1 -= w->attrib.x + w->attrib.border_width;
    box.y1 -= w->attrib.y + w->attrib.border_width;
    box.x2 -= w->attrib.x + w->attrib.border_width;
    box.y2 -= w->attrib.y + w->attrib.border_width;

    addWindowDamageRect(w, &box);
}

AnimEffectProperties animEffectProperties[AnimEffectNum] = {
    // AnimEffectNone
    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
    // AnimEffectRandom
    {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
    // AnimEffectAirplane3D
    {0, polygonsPrePaintWindow, polygonsPostPaintWindow, fxAirplane3DAnimStep,
     fxAirplane3DInit, 0, polygonsStoreClips, polygonsDrawCustomGeometry, 0,
     fxAirplane3DLinearAnimStepPolygon, 0, 0, 0, updateBBScreen},
    // AnimEffectBeamUp
    {fxBeamupUpdateWindowAttrib, 0, drawParticleSystems, fxBeamUpModelStep,
     fxBeamUpInit, 0, 0, 0, 1, 0, 0, 0, 0, particlesUpdateBB},
    // AnimEffectBurn
    {0, 0, drawParticleSystems, fxBurnModelStep, fxBurnInit, 0, 0, 0, 1, 0,
     0, 0, 0, particlesUpdateBB},
    // AnimEffectCurvedFold
    {fxFoldUpdateWindowAttrib, 0, 0, fxCurvedFoldModelStep,
     defaultMinimizeAnimInit, fxMagicLampInitGrid, 0, 0, 0, 0, 0,
     defaultMinimizeUpdateWindowTransform, 0, modelUpdateBB},
    // AnimEffectDodge
    {0, 0, 0, fxDodgeAnimStep, defaultAnimInit, 0, 0, 0, 0, 0,
     defaultLetOthersDrawGeoms,
     fxDodgeUpdateWindowTransform, fxDodgePostPreparePaintScreen,
     fxDodgeUpdateBB},
    // AnimEffectDomino3D
    {0, polygonsPrePaintWindow, polygonsPostPaintWindow, polygonsAnimStep,
     fxDomino3DInit, 0, polygonsStoreClips, polygonsDrawCustomGeometry, 0,
     polygonsLinearAnimStepPolygon, 0, 0, 0, polygonsUpdateBB},
    // AnimEffectDream
    {fxDreamUpdateWindowAttrib, 0, 0, fxDreamModelStep, fxDreamAnimInit,
     fxMagicLampInitGrid, 0, 0, 0, 0, 0, defaultMinimizeUpdateWindowTransform,
     0, modelUpdateBB},
    // AnimEffectExplode3D
    {0, polygonsPrePaintWindow, polygonsPostPaintWindow, polygonsAnimStep,
     fxExplode3DInit, 0, polygonsStoreClips, polygonsDrawCustomGeometry, 0,
     polygonsLinearAnimStepPolygon, 0, 0, 0, polygonsUpdateBB},
    // AnimEffectFade
    {fxFadeUpdateWindowAttrib, 0, 0, defaultAnimStep, defaultAnimInit, 0, 0,
     0, 0, 0, defaultLetOthersDrawGeoms, 0, 0, updateBBWindow},
    // AnimEffectFocusFade
    {fxFocusFadeUpdateWindowAttrib, 0, 0, defaultAnimStep, defaultAnimInit,
     0, 0, 0, 0, 0, defaultLetOthersDrawGeoms, 0, 0, updateBBWindow},
    // AnimEffectFold3D
    {0, polygonsPrePaintWindow, polygonsPostPaintWindow, polygonsAnimStep,
    fxFold3DInit, 0, polygonsStoreClips, polygonsDrawCustomGeometry, 0,
    fxFold3dAnimStepPolygon, 0, 0, 0, polygonsUpdateBB},
    // AnimEffectGlide3D1
    {fxGlideUpdateWindowAttrib, fxGlidePrePaintWindow,
     fxGlidePostPaintWindow, fxGlideAnimStep,
     fxGlideInit, 0, polygonsStoreClips, polygonsDrawCustomGeometry, 0,
     polygonsDeceleratingAnimStepPolygon,
     fxGlideLetOthersDrawGeoms, fxGlideUpdateWindowTransform, 0,
     fxGlideUpdateBB},
    // AnimEffectGlide3D2
    {fxGlideUpdateWindowAttrib, fxGlidePrePaintWindow,
     fxGlidePostPaintWindow, fxGlideAnimStep,
     fxGlideInit, 0, polygonsStoreClips, polygonsDrawCustomGeometry, 0,
     polygonsDeceleratingAnimStepPolygon,
     fxGlideLetOthersDrawGeoms, fxGlideUpdateWindowTransform, 0,
     fxGlideUpdateBB},
    // AnimEffectHorizontalFolds
    {fxFoldUpdateWindowAttrib, 0, 0, fxHorizontalFoldsModelStep,
     defaultMinimizeAnimInit, fxHorizontalFoldsInitGrid, 0, 0, 0, 0, 0,
     defaultMinimizeUpdateWindowTransform, 0, modelUpdateBB},
    // AnimEffectLeafSpread3D
    {0, polygonsPrePaintWindow, polygonsPostPaintWindow, polygonsAnimStep,
     fxLeafSpread3DInit, 0, polygonsStoreClips, polygonsDrawCustomGeometry, 0,
     polygonsLinearAnimStepPolygon, 0, 0, 0, polygonsUpdateBB},
    // AnimEffectMagicLamp
    {0, 0, 0, fxMagicLampModelStep, fxMagicLampInit, fxMagicLampInitGrid,
     0, 0, 0, 0, 0, 0, 0, modelUpdateBB},
    // AnimEffectRazr3D
    {0, polygonsPrePaintWindow, polygonsPostPaintWindow, polygonsAnimStep,
     fxDomino3DInit, 0, polygonsStoreClips, polygonsDrawCustomGeometry, 0,
     polygonsLinearAnimStepPolygon, 0, 0, 0, polygonsUpdateBB},
    // AnimEffectRollUp
    {0, 0, 0, fxRollUpModelStep, fxRollUpAnimInit, fxRollUpInitGrid, 0, 0, 1,
     0, 0, 0, 0, modelUpdateBB},
    // AnimEffectSidekick
    {fxZoomUpdateWindowAttrib, 0, 0, defaultAnimStep, fxSidekickInit,
     0, 0, 0, 1, 0, defaultLetOthersDrawGeoms, fxZoomUpdateWindowTransform,
     0, compTransformUpdateBB},
    // AnimEffectSkewer
    {0, polygonsPrePaintWindow, polygonsPostPaintWindow, polygonsAnimStep,
    fxSkewerInit, 0, polygonsStoreClips, polygonsDrawCustomGeometry, 0,
    fxSkewerAnimStepPolygon, 0, 0, 0, polygonsUpdateBB},
    // AnimEffectVacuum
    {0, 0, 0, fxMagicLampModelStep, fxMagicLampInit,
     fxVacuumInitGrid, 0, 0, 0, 0, 0, 0, 0, modelUpdateBB},
    // AnimEffectWave
    {0, 0, 0, fxWaveModelStep, 0, fxMagicLampInitGrid, 0, 0, 0, 0, 0, 0, 0,
     modelUpdateBB},
    // AnimEffectZoom
    {fxZoomUpdateWindowAttrib, 0, 0, defaultAnimStep, fxZoomInit,
     0, 0, 0, 1, 0, defaultLetOthersDrawGeoms, fxZoomUpdateWindowTransform,
     0, compTransformUpdateBB}
};


Bool getMousePointerXY(CompScreen * s, short *x, short *y)
{
    Window w1, w2;
    int xp, yp, xj, yj;
    unsigned int m;

    if (XQueryPointer
	(s->display->display, s->root, &w1, &w2, &xj, &yj, &xp, &yp, &m))
    {
	*x = xp;
	*y = yp;
	return TRUE;
    }
    return FALSE;
}

static int animGetWindowState(CompWindow * w)
{
    Atom actual;
    int result, format;
    unsigned long n, left;
    unsigned char *data;

    result = XGetWindowProperty(w->screen->display->display, w->id,
				w->screen->display->wmStateAtom, 0L,
				1L, FALSE,
				w->screen->display->wmStateAtom,
				&actual, &format, &n, &left, &data);

    if (result == Success && n && data)
    {
	int state;

	memcpy(&state, data, sizeof(int));
	XFree((void *)data);

	return state;
    }

    return WithdrawnState;
}

static Bool
animSetScreenOptions(CompPlugin *plugin,
		     CompScreen * screen,
		     char *name,
		     CompOptionValue * value)
{
    CompOption *o;
    int index;

    ANIM_SCREEN(screen);

    o = compFindOption(as->opt, NUM_OPTIONS(as), name, &index);
    if (!o)
	return FALSE;

    switch (index)
    {
    case ANIM_SCREEN_OPTION_OPEN_MATCHES:
    case ANIM_SCREEN_OPTION_CLOSE_MATCHES:
    case ANIM_SCREEN_OPTION_MINIMIZE_MATCHES:
    case ANIM_SCREEN_OPTION_FOCUS_MATCHES:
    case ANIM_SCREEN_OPTION_SHADE_MATCHES:
	if (compSetOptionList(o, value))
	{
	    int i;
	    for (i = 0; i < o->value.list.nValue; i++)
		matchUpdate (screen->display, &o->value.list.value[i].match);
	}
	break;
    case ANIM_SCREEN_OPTION_OPEN_OPTIONS:
	if (compSetOptionList(o, value))
	{
	    updateOptionSets
		(screen, as->eventOptionSets[WindowEventOpen], &o->value.list);
	}
	break;
    case ANIM_SCREEN_OPTION_CLOSE_OPTIONS:
	if (compSetOptionList(o, value))
	{
	    updateOptionSets
		(screen, as->eventOptionSets[WindowEventClose], &o->value.list);
	}
	break;
    case ANIM_SCREEN_OPTION_MINIMIZE_OPTIONS:
	if (compSetOptionList(o, value))
	{
	    updateOptionSets
		(screen, as->eventOptionSets[WindowEventMinimize], &o->value.list);
	}
	break;
    case ANIM_SCREEN_OPTION_FOCUS_OPTIONS:
	if (compSetOptionList(o, value))
	{
	    updateOptionSets
		(screen, as->eventOptionSets[WindowEventFocus], &o->value.list);
	}
	break;
    case ANIM_SCREEN_OPTION_SHADE_OPTIONS:
	if (compSetOptionList(o, value))
	{
	    updateOptionSets
		(screen, as->eventOptionSets[WindowEventShade], &o->value.list);
	}
	break;
    case ANIM_SCREEN_OPTION_OPEN_RANDOM_EFFECTS:
	if (compSetOptionList(o, value))
	{
	    animStoreRandomEffectList (&o->value,
				       closeEffects + RANDOM_EFFECT_OFFSET,
				       NUM_CLOSE_EFFECT - RANDOM_EFFECT_OFFSET,
				       as->openRandomEffects,
				       &as->nOpenRandomEffects);
	    return TRUE;
	}
	break;
    case ANIM_SCREEN_OPTION_CLOSE_RANDOM_EFFECTS:
	if (compSetOptionList(o, value))
	{
	    animStoreRandomEffectList (&o->value,
				       closeEffects + RANDOM_EFFECT_OFFSET,
				       NUM_CLOSE_EFFECT - RANDOM_EFFECT_OFFSET,
				       as->closeRandomEffects,
				       &as->nCloseRandomEffects);
	    return TRUE;
	}
	break;
    case ANIM_SCREEN_OPTION_MINIMIZE_RANDOM_EFFECTS:
	if (compSetOptionList(o, value))
	{
	    animStoreRandomEffectList (&o->value,
				       minimizeEffects + RANDOM_EFFECT_OFFSET,
				       NUM_MINIMIZE_EFFECT - RANDOM_EFFECT_OFFSET,
				       as->minimizeRandomEffects,
				       &as->nMinimizeRandomEffects);
	    return TRUE;
	}
	break;
    case ANIM_SCREEN_OPTION_SHADE_RANDOM_EFFECTS:
	if (compSetOptionList(o, value))
	{
	    animStoreRandomEffectList (&o->value,
				       shadeEffects + RANDOM_EFFECT_OFFSET,
				       NUM_SHADE_EFFECT - RANDOM_EFFECT_OFFSET,
				       as->shadeRandomEffects,
				       &as->nShadeRandomEffects);
	    return TRUE;
	}
	break;
    default:
	return compSetScreenOption (screen, o, value);
	break;
    }

    return FALSE;
}

static const CompMetadataOptionInfo animScreenOptionInfo[] = {
    // Event settings
    { "open_effects", "list", "<type>int</type>" RESTOSTRING (0, LAST_CLOSE_EFFECT), 0, 0 },
    { "open_durations", "list", "<type>int</type><min>50</min>", 0, 0 },
    { "open_matches", "list", "<type>match</type>", 0, 0 },
    { "open_options", "list", "<type>string</type>", 0, 0 },
    { "open_random_effects", "list", "<type>int</type>" RESTOSTRING (0, LAST_RANDOM_CLOSE_EFFECT), 0, 0 },
    { "close_effects", "list", "<type>int</type>" RESTOSTRING (0, LAST_CLOSE_EFFECT), 0, 0 },
    { "close_durations", "list", "<type>int</type><min>50</min>", 0, 0 },
    { "close_matches", "list", "<type>match</type>", 0, 0 },
    { "close_options", "list", "<type>string</type>", 0, 0 },
    { "close_random_effects", "list", "<type>int</type>" RESTOSTRING (0, LAST_RANDOM_CLOSE_EFFECT), 0, 0 },
    { "minimize_effects", "list", "<type>int</type>" RESTOSTRING (0, LAST_MINIMIZE_EFFECT), 0, 0 },
    { "minimize_durations", "list", "<type>int</type><min>50</min>", 0, 0 },
    { "minimize_matches", "list", "<type>match</type>", 0, 0 },
    { "minimize_options", "list", "<type>string</type>", 0, 0 },
    { "minimize_random_effects", "list", "<type>int</type>" RESTOSTRING (0, LAST_RANDOM_MINIMIZE_EFFECT), 0, 0 },
    { "focus_effects", "list", "<type>int</type>" RESTOSTRING (0, LAST_FOCUS_EFFECT), 0, 0 },
    { "focus_durations", "list", "<type>int</type><min>50</min>", 0, 0 },
    { "focus_matches", "list", "<type>match</type>", 0, 0 },
    { "focus_options", "list", "<type>string</type>", 0, 0 },
    { "shade_effects", "list", "<type>int</type>" RESTOSTRING (0, LAST_SHADE_EFFECT), 0, 0 },
    { "shade_durations", "list", "<type>int</type><min>50</min>", 0, 0 },
    { "shade_matches", "list", "<type>match</type>", 0, 0 },
    { "shade_options", "list", "<type>string</type>", 0, 0 },
    { "shade_random_effects", "list", "<type>int</type>" RESTOSTRING (0, LAST_RANDOM_SHADE_EFFECT), 0, 0 },
    // Misc. settings
    { "all_random", "bool", 0, 0, 0 },
    { "time_step", "int", "<min>1</min>", 0, 0 },
    { "time_step_intense", "int", "<min>1</min>", 0, 0 },
    // Effect settings
    { "airplane_path_length", "float", "<min>0.2</min>", 0, 0 },
    { "airplane_fly_to_taskbar", "bool", 0, 0, 0 },
    { "beam_size", "float", "<min>0.1</min>", 0, 0 },
    { "beam_spacing", "int", "<min>1</min>", 0, 0 },
    { "beam_color", "color", 0, 0, 0 },
    { "beam_slowdown", "float", "<min>0.1</min>", 0, 0 },
    { "beam_life", "float", "<min>0.1</min>", 0, 0 },
    { "curved_fold_amp", "float", "<min>-0.5</min><max>0.5</max>", 0, 0 },
    { "curved_fold_zoom_to_taskbar", "bool", 0, 0, 0 },
    { "dodge_gap_ratio", "float", "<min>0.0</min><max>1.0</max>", 0, 0 },
    { "domino_direction", "int", RESTOSTRING (0, LAST_ANIM_DIRECTION), 0, 0 },
    { "dream_zoom_to_taskbar", "bool", 0, 0, 0 },
    { "razr_direction", "int", RESTOSTRING (0, LAST_ANIM_DIRECTION), 0, 0 },
    { "explode_thickness", "float", "<min>1</min>", 0, 0 },
    { "explode_gridx", "int", "<min>1</min>", 0, 0 },
    { "explode_gridy", "int", "<min>1</min>", 0, 0 },
    { "explode_tessellation", "int", RESTOSTRING (0, LAST_POLYGON_TESS), 0, 0 },
    { "fire_particles", "int", "<min>0</min>", 0, 0 },
    { "fire_size", "float", "<min>0.1</min>", 0, 0 },
    { "fire_slowdown", "float", "<min>0.1</min>", 0, 0 },
    { "fire_life", "float", "<min>0.1</min>", 0, 0 },
    { "fire_color", "color", 0, 0, 0 },
    { "fire_direction", "int", RESTOSTRING (0, LAST_ANIM_DIRECTION), 0, 0 },
    { "fire_constant_speed", "bool", 0, 0, 0 },
    { "fire_smoke", "bool", 0, 0, 0 },
    { "fire_mystical", "bool", 0, 0, 0 },
    { "fold_gridx", "int", "<min>1</min>", 0, 0 },
    { "fold_gridy", "int", "<min>1</min>", 0, 0 },
    { "fold_dir", "int", "<min>0</min>", 0, 0 },
    { "glide1_away_position", "float", 0, 0, 0 },
    { "glide1_away_angle", "float", 0, 0, 0 },
    { "glide1_thickness", "float", "<min>0</min>", 0, 0 },
    { "glide1_zoom_to_taskbar", "bool", 0, 0, 0 },
    { "glide2_away_position", "float", 0, 0, 0 },
    { "glide2_away_angle", "float", 0, 0, 0 },
    { "glide2_thickness", "float", "<min>0</min>", 0, 0 },
    { "glide2_zoom_to_taskbar", "bool", 0, 0, 0 },
    { "horizontal_folds_amp", "float", "<min>-0.5</min><max>0.5</max>", 0, 0 },
    { "horizontal_folds_num_folds", "int", "<min>1</min>", 0, 0 },
    { "horizontal_folds_zoom_to_taskbar", "bool", 0, 0, 0 },
    { "magic_lamp_moving_end", "bool", 0, 0, 0 },
    { "magic_lamp_grid_res", "int", "<min>4</min>", 0, 0 },
    { "magic_lamp_max_waves", "int", "<min>3</min>", 0, 0 },
    { "magic_lamp_amp_min", "float", "<min>200</min>", 0, 0 },
    { "magic_lamp_amp_max", "float", "<min>200</min>", 0, 0 },
    { "magic_lamp_open_start_width", "int", "<min>0</min>", 0, 0 },
    { "rollup_fixed_interior", "bool", 0, 0, 0 },
    { "sidekick_num_rotations", "float", "<min>0</min>", 0, 0 },
    { "sidekick_springiness", "float", "<min>0</min><max>1</max>", 0, 0 },
    { "sidekick_zoom_from_center", "int", RESTOSTRING (0, LAST_ZOOM_FROM_CENTER), 0, 0 },
    { "skewer_gridx", "int", "<min>1</min>", 0, 0 },
    { "skewer_gridy", "int", "<min>1</min>", 0, 0 },
    { "skewer_thickness", "float", "<min>1</min>", 0, 0 },
    { "skewer_direction", "int", "<min>0</min>", 0, 0 },
    { "skewer_tessellation", "int", RESTOSTRING (0, LAST_POLYGON_TESS), 0, 0 },
    { "skewer_rotation", "int", 0, 0, 0 },
    { "vacuum_moving_end", "bool", 0, 0, 0 },
    { "vacuum_grid_res", "int", "<min>4</min>", 0, 0 },
    { "vacuum_open_start_width", "int", "<min>0</min>", 0, 0 },
    { "wave_width", "float", "<min>0</min>", 0, 0 },
    { "wave_amp", "float", "<min>0</min>", 0, 0 },
    { "zoom_from_center", "int", RESTOSTRING (0, LAST_ZOOM_FROM_CENTER), 0, 0 },
    { "zoom_springiness", "float", "<min>0</min><max>1</max>", 0, 0 }
};

static CompOption *
animGetScreenOptions(CompPlugin *plugin, CompScreen * screen, int *count)
{
    ANIM_SCREEN(screen);

    *count = NUM_OPTIONS(as);
    return as->opt;
}

static void
objectInit(Object * object,
	   float positionX, float positionY,
	   float gridPositionX, float gridPositionY)
{
    object->gridPosition.x = gridPositionX;
    object->gridPosition.y = gridPositionY;

    object->position.x = positionX;
    object->position.y = positionY;

    object->offsetTexCoordForQuadBefore.x = 0;
    object->offsetTexCoordForQuadBefore.y = 0;
    object->offsetTexCoordForQuadAfter.x = 0;
    object->offsetTexCoordForQuadAfter.y = 0;
}

void
modelInitObjects(Model * model, int x, int y, int width, int height)
{
    int gridX, gridY;
    int nGridCellsX, nGridCellsY;
    float x0, y0;

    x0 = model->scaleOrigin.x;
    y0 = model->scaleOrigin.y;

    // number of grid cells in x direction
    nGridCellsX = model->gridWidth - 1;

    if (model->forWindowEvent == WindowEventShade ||
	model->forWindowEvent == WindowEventUnshade)
    {
	// number of grid cells in y direction
	nGridCellsY = model->gridHeight - 3;	// One allocated for top, one for bottom

	float winContentsHeight =
	    height - model->topHeight - model->bottomHeight;

	//Top
	float objectY = y + (0 - y0) * model->scale.y + y0;

	for (gridX = 0; gridX < model->gridWidth; gridX++)
	{
	    objectInit(&model->objects[gridX],
		       x + ((gridX * width / nGridCellsX) - x0) * 
		       model->scale.x + x0, objectY,
		       (float)gridX / nGridCellsX, 0);
	}

	// Window contents
	for (gridY = 1; gridY < model->gridHeight - 1; gridY++)
	{
	    float inWinY =
		(gridY - 1) * winContentsHeight / nGridCellsY +
		model->topHeight;
	    float gridPosY = inWinY / height;

	    objectY = y + (inWinY - y0) * model->scale.y + y0;

	    for (gridX = 0; gridX < model->gridWidth; gridX++)
	    {
		objectInit(&model->objects[gridY * model->gridWidth + gridX],
			   x + ((gridX * width / nGridCellsX) - x0) * 
			   model->scale.x + x0,
			   objectY, (float)gridX / nGridCellsX, gridPosY);
	    }
	}

	// Bottom (gridY is model->gridHeight-1 now)
	objectY = y + (height - y0) * model->scale.y + y0;

	for (gridX = 0; gridX < model->gridWidth; gridX++)
	{
	    objectInit(&model->objects[gridY * model->gridWidth + gridX],
		       x + ((gridX * width / nGridCellsX) - x0) * 
		       model->scale.x + x0, objectY,
		       (float)gridX / nGridCellsX, 1);
	}
    }
    else
    {
	// number of grid cells in y direction
	nGridCellsY = model->gridHeight - 1;

	int i = 0;

	for (gridY = 0; gridY < model->gridHeight; gridY++)
	{
	    float objectY =
		y + ((gridY * height / nGridCellsY) -
		     y0) * model->scale.y + y0;
	    for (gridX = 0; gridX < model->gridWidth; gridX++)
	    {
		objectInit(&model->objects[i],
			   x + ((gridX * width / nGridCellsX) - x0) * 
			   model->scale.x + x0,
			   objectY,
			   (float)gridX / nGridCellsX,
			   (float)gridY / nGridCellsY);
		i++;
	    }
	}
    }
}

static void
modelMove (Model *model,
	   float tx,
	   float ty)
{
    int i;

    for (i = 0; i < model->numObjects; i++)
    {
	model->objects[i].position.x += tx;
	model->objects[i].position.y += ty;
    }
}

static Model *createModel(CompWindow * w,
			  WindowEvent forWindowEvent,
			  AnimEffect forAnimEffect, int gridWidth,
			  int gridHeight)
{
    int x = WIN_X(w);
    int y = WIN_Y(w);
    int width = WIN_W(w);
    int height = WIN_H(w);

    Model *model;

    model = calloc(1, sizeof(Model));
    if (!model)
    {
	compLogMessage (w->screen->display, "animation", CompLogLevelError,
			"Not enough memory");
	return 0;
    }
    model->magicLampWaveCount = 0;
    model->magicLampWaves = NULL;

    model->gridWidth = gridWidth;
    model->gridHeight = gridHeight;
    model->numObjects = gridWidth * gridHeight;
    model->objects = calloc(model->numObjects, sizeof(Object));
    if (!model->objects)
    {
	compLogMessage (w->screen->display, "animation", CompLogLevelError,
			"Not enough memory");
	free(model);
	return 0;
    }

    // Store win. size to check later
    model->winWidth = width;
    model->winHeight = height;

    // For shading
    model->forWindowEvent = forWindowEvent;
    model->topHeight = w->output.top;
    model->bottomHeight = w->output.bottom;

    model->scale.x = 1.0f;
    model->scale.y = 1.0f;

    model->scaleOrigin.x = 0.0f;
    model->scaleOrigin.y = 0.0f;

    modelInitObjects(model, x, y, width, height);

    return model;
}

static void
animFreeModel(AnimWindow *aw)
{
    if (!aw->model)
	return;

    if (aw->model->magicLampWaves)
	free(aw->model->magicLampWaves);
    if (aw->model->objects)
	free(aw->model->objects);
    free(aw->model);
    aw->model = NULL;
}

static Bool
animEnsureModel(CompWindow * w)
{
    ANIM_WINDOW(w);
    ANIM_SCREEN(w->screen);

    WindowEvent forWindowEvent = aw->curWindowEvent;
    AnimEffect forAnimEffect = aw->curAnimEffect;
	
    int gridWidth = 2;
    int gridHeight = 2;

    if (animEffectProperties[forAnimEffect].initGridFunc)
	animEffectProperties[forAnimEffect].initGridFunc
	    (as, aw, &gridWidth, &gridHeight);

    Bool isShadeUnshadeEvent =
	(forWindowEvent == WindowEventShade ||
	 forWindowEvent == WindowEventUnshade);

    Bool wasShadeUnshadeEvent = aw->model &&
	(aw->model->forWindowEvent == WindowEventShade ||
	 aw->model->forWindowEvent == WindowEventUnshade);

    if (!aw->model ||
	gridWidth != aw->model->gridWidth ||
	gridHeight != aw->model->gridHeight ||
	(isShadeUnshadeEvent != wasShadeUnshadeEvent) ||
	aw->model->winWidth != WIN_W(w) || aw->model->winHeight != WIN_H(w))
    {
	animFreeModel(aw);
	aw->model = createModel(w,
				forWindowEvent, forAnimEffect,
				gridWidth, gridHeight);
	if (!aw->model)
	    return FALSE;
    }

    return TRUE;
}

static Bool playingPolygonEffect(AnimScreen *as, AnimWindow *aw)
{
    if (!animEffectProperties[aw->curAnimEffect].
	addCustomGeometryFunc)
	return FALSE;

    if (!(aw->curAnimEffect == AnimEffectGlide3D1 ||
	  aw->curAnimEffect == AnimEffectGlide3D2))
	return TRUE;

    return (fxGlideIsPolygonBased(as, aw));
}

static void cleanUpParentChildChainItem(AnimScreen *as, AnimWindow *aw)
{
    if (aw->winThisIsPaintedBefore && !aw->winThisIsPaintedBefore->destroyed)
    {
	AnimWindow *aw2 =
	    GET_ANIM_WINDOW(aw->winThisIsPaintedBefore, as);
	if (aw2)
	    aw2->winToBePaintedBeforeThis = NULL;
    }
    aw->winThisIsPaintedBefore = NULL;
    aw->moreToBePaintedPrev = NULL;
    aw->moreToBePaintedNext = NULL;
    aw->isDodgeSubject = FALSE;
    aw->skipPostPrepareScreen = FALSE;
}

static void postAnimationCleanupCustom(CompWindow * w,
       				       Bool resetAnimation,
       				       Bool closing,
       				       Bool clearMatchingRow)
{
    ANIM_WINDOW(w);
    ANIM_SCREEN(w->screen);

    if (// make sure window shadows (which are not drawn by polygon engine)
	// are damaged
	(playingPolygonEffect(as, aw) &&
	 (aw->curWindowEvent == WindowEventOpen ||
	  aw->curWindowEvent == WindowEventUnminimize ||
	  aw->curWindowEvent == WindowEventUnshade ||
	  aw->curWindowEvent == WindowEventFocus)) ||
	// make sure the window gets fully damaged with
	// effects that possibly have models that don't cover
	// the whole window (like in magic lamp with menus)
	aw->curAnimEffect == AnimEffectMagicLamp ||
	aw->curAnimEffect == AnimEffectVacuum)
    {
	updateBBWindow (NULL, w);
    }

    if (resetAnimation)
    {
	aw->curWindowEvent = WindowEventNone;
	aw->curAnimEffect = AnimEffectNone;
	aw->animOverrideProgressDir = 0;

	if (aw->model)
	{
	    if (aw->model->magicLampWaves)
		free(aw->model->magicLampWaves);
	    aw->model->magicLampWaves = 0;
	}
    }

    if (aw->BB.x1 != MAXSHORT)
    {
	// damage BB
	memcpy (&aw->lastBB, &aw->BB, sizeof(Box));
	damageBoundingBox (w);
    }
    aw->BB.x1 = aw->BB.y1 = MAXSHORT;
    aw->BB.x2 = aw->BB.y2 = MINSHORT;

    Bool thereIsUnfinishedChainElem = FALSE;

    // Look for still playing windows in parent-child chain
    CompWindow *wCur = aw->moreToBePaintedNext;
    while (wCur)
    {
	AnimWindow *awCur = GET_ANIM_WINDOW(wCur, as);

	if (awCur->animRemainingTime > 0)
	{
	    thereIsUnfinishedChainElem = TRUE;
	    break;
	}
	wCur = awCur->moreToBePaintedNext;
    }
    if (!thereIsUnfinishedChainElem)
    {
	wCur = aw->moreToBePaintedPrev;
	while (wCur)
	{
	    AnimWindow *awCur = GET_ANIM_WINDOW(wCur, as);

	    if (awCur->animRemainingTime > 0)
	    {
		thereIsUnfinishedChainElem = TRUE;
		break;
	    }
	    wCur = awCur->moreToBePaintedPrev;
	}
    }
    if (closing || !thereIsUnfinishedChainElem)
    {
	// Finish off all windows in parent-child chain
	CompWindow *wCur = aw->moreToBePaintedNext;
	while (wCur)
	{
	    AnimWindow *awCur = GET_ANIM_WINDOW(wCur, as);
	    wCur = awCur->moreToBePaintedNext;
	    cleanUpParentChildChainItem(as, awCur);
	}
	wCur = w;
	while (wCur)
	{
	    AnimWindow *awCur = GET_ANIM_WINDOW(wCur, as);
	    wCur = awCur->moreToBePaintedPrev;
	    cleanUpParentChildChainItem(as, awCur);
	}
    }

    aw->state = aw->newState;

    if (clearMatchingRow)
	aw->curAnimSelectionRow = -1;

    if (aw->drawRegion)
	XDestroyRegion(aw->drawRegion);
    aw->drawRegion = NULL;
    aw->useDrawRegion = FALSE;

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

	for (i = 0; i < aw->numPs; i++)
	    finiParticles(aw->ps + i);
	free(aw->ps);
	aw->ps = NULL;
	aw->numPs = 0;
    }

    if (aw->polygonSet)
    {
	freePolygonSet(aw);
	//aw->polygonSet->nClips = 0;
    }
    aw->animInitialized = FALSE;
    aw->remainderSteps = 0;

    // Reset dodge parameters
    aw->dodgeMaxAmount = 0;
    if (!(aw->moreToBePaintedPrev ||
	  aw->moreToBePaintedNext))
    {
	aw->isDodgeSubject = FALSE;
	aw->skipPostPrepareScreen = FALSE;
    }

    if (aw->restackInfo)
    {
	free(aw->restackInfo);
	aw->restackInfo = NULL;
    }

    //if (aw->unmapCnt || aw->destroyCnt)
    //    releaseWindow (w);
    while (aw->unmapCnt)
    {
	unmapWindow(w);
	aw->unmapCnt--;
    }
    while (aw->destroyCnt)
    {
	destroyWindow(w);
	aw->destroyCnt--;
    }
}

void postAnimationCleanup(CompWindow * w, Bool resetAnimation)
{
    postAnimationCleanupCustom(w, resetAnimation, FALSE, TRUE);
}

static void
animActivateEvent (CompScreen *s,
		   Bool       activating)
{
    ANIM_SCREEN(s);
	
    if (activating)
    {
	if (as->animInProgress)
	    return;
    }
    as->animInProgress = activating;

    CompOption o[2];

    o[0].type = CompOptionTypeInt;
    o[0].name = "root";
    o[0].value.i = s->root;

    o[1].type = CompOptionTypeBool;
    o[1].name = "active";
    o[1].value.b = activating;

    (*s->display->handleCompizEvent) (s->display, "animation", "activate", o, 2);
}

static inline Bool
isWinVisible(CompWindow *w)
{
    return (!w->destroyed &&
	    !(!w->shaded &&
	      (w->attrib.map_state != IsViewable)));
}

static inline void
getHostedOnWin (AnimScreen *as,
		CompWindow *w,
		CompWindow *wHost)
{
    ANIM_WINDOW(w);
    AnimWindow *awHost = GET_ANIM_WINDOW(wHost, as);
    awHost->winToBePaintedBeforeThis = w;
    aw->winThisIsPaintedBefore = wHost;
}

static Bool
otherPluginsActive(AnimScreen *as)
{
    int i;
    for (i = 0; i < NUM_WATCHED_PLUGINS; i++)
	if (as->pluginActive[i])
	    return TRUE;
    return FALSE;
}

static void
initiateFocusAnimation(CompWindow *w)
{
    CompScreen *s = w->screen;
    ANIM_SCREEN(s);
    ANIM_WINDOW(w);

    if (aw->curWindowEvent != WindowEventNone || otherPluginsActive(as))
	return;

    // Check the "switcher post-wait" counter that effectively prevents
    // focus animation to be initiated when the zoom option value is low
    // in Switcher.
    if (switcherPostWait)
	return;

    int duration = 200;
    AnimEffect chosenEffect =
	getMatchingAnimSelection (w, WindowEventFocus, &duration);

    if (chosenEffect &&
	// On unminimization, focus event is fired first.
	// When this happens and minimize is in progress,
	// don't prevent rewinding of minimize when unminimize is fired
	// right after this focus event.
	aw->curWindowEvent != WindowEventMinimize)
    {
	CompWindow *wStart = NULL;
	CompWindow *wEnd = NULL;
	CompWindow *wOldAbove = NULL;

	RestackInfo *restackInfo = aw->restackInfo;
	Bool raised = TRUE;

	if (restackInfo)
	{
	    wStart = restackInfo->wStart;
	    wEnd = restackInfo->wEnd;
	    wOldAbove = restackInfo->wOldAbove;
	    raised = restackInfo->raised;
	}

	// FOCUS event!

	aw->curWindowEvent = WindowEventFocus;
	aw->curAnimEffect = chosenEffect;

	if (chosenEffect == AnimEffectFocusFade ||
	    chosenEffect == AnimEffectDodge)
	{
	    // Find union region of all windows that will be
	    // faded through by w. If the region is empty, don't
	    // run focus fade effect.

	    Region fadeRegion = XCreateRegion();
	    Region thisAndSubjectIntersection = XCreateRegion();
	    Region thisWinRegion = XCreateRegion();
	    Region subjectWinRegion = XCreateRegion();
	    XRectangle rect;

	    int numDodgingWins = 0;

	    // Compute subject win. region
	    rect.x = WIN_X(w);
	    rect.y = WIN_Y(w);
	    rect.width = WIN_W(w);
	    rect.height = WIN_H(w);
	    XUnionRectWithRegion(&rect, &emptyRegion, subjectWinRegion);

	    // Dodge candidate window
	    CompWindow *dw;
	    for (dw = wStart; dw && dw != wEnd->next; dw = dw->next)
	    {
		if (!isWinVisible(dw) ||
		    dw->wmType & CompWindowTypeDockMask)
		    continue;

		// Compute intersection of this with subject
		rect.x = WIN_X(dw);
		rect.y = WIN_Y(dw);
		rect.width = WIN_W(dw);
		rect.height = WIN_H(dw);
		XUnionRectWithRegion(&rect, &emptyRegion, thisWinRegion);
		XIntersectRegion(subjectWinRegion, thisWinRegion,
				 thisAndSubjectIntersection);
		XUnionRegion(fadeRegion, thisAndSubjectIntersection,
			     fadeRegion);

		if (chosenEffect == AnimEffectDodge &&
		    !XEmptyRegion(thisAndSubjectIntersection))
		{
		    AnimWindow *adw = GET_ANIM_WINDOW(dw, as);
		    if ((adw->curAnimEffect == AnimEffectNone ||
			 (adw->curAnimEffect == AnimEffectDodge)) &&
			dw->id != w->id) // don't let the subject dodge itself
		    {
			// Mark this window for dodge

			numDodgingWins++;
			adw->dodgeOrder = numDodgingWins;
		    }
		}
	    }

	    if (XEmptyRegion(fadeRegion))
		return; // empty -> won't be drawn

	    if ((chosenEffect == AnimEffectFocusFade ||
		 chosenEffect == AnimEffectDodge) && wOldAbove)
	    {
		// Store this window in the next window
		// so that this is drawn before that,
		// i.e. in its old place
		getHostedOnWin(as, w, wOldAbove);
	    }

	    float dodgeMaxStartProgress =
		numDodgingWins *
		animGetF(as, aw, ANIM_SCREEN_OPTION_DODGE_GAP_RATIO) *
		duration / 1000.0f;

	    if (chosenEffect == AnimEffectDodge)
	    {
		CompWindow *wDodgeChainLastVisited = NULL;

		animActivateEvent(s, TRUE);

		aw->isDodgeSubject = TRUE;
		aw->dodgeChainStart = NULL;
		float maxTransformTotalProgress = 0;

		for (dw = wStart; dw && dw != wEnd->next; dw = dw->next)
		{
		    AnimWindow *adw = GET_ANIM_WINDOW(dw, as);

		    // Skip non-dodgers
		    if (adw->dodgeOrder == 0)
			continue;

		    // Initiate dodge for this window

		    adw->dodgeSubjectWin = w;
		    adw->curAnimEffect = AnimEffectDodge;

		    // Slight change in dodge movement start
		    // to reflect stacking order of dodgy windows
		    if (raised)
			adw->transformStartProgress =
			    dodgeMaxStartProgress *
			    (adw->dodgeOrder - 1) / numDodgingWins;
		    else
			adw->transformStartProgress =
			    dodgeMaxStartProgress *
			    (1 - (float)adw->dodgeOrder / numDodgingWins);

		    float transformTotalProgress =
			1 + adw->transformStartProgress;

		    // normalize
		    adw->transformStartProgress /=
			transformTotalProgress;

		    adw->animTotalTime =
			transformTotalProgress * duration;
		    adw->animRemainingTime = adw->animTotalTime;

		    if (maxTransformTotalProgress < transformTotalProgress)
			maxTransformTotalProgress = transformTotalProgress;

		    // Put window on dodge chain

		    // if dodge chain was started before
		    if (wDodgeChainLastVisited)
		    {
			AnimWindow *awDodgeChainLastVisited =
			    GET_ANIM_WINDOW(wDodgeChainLastVisited, as);
			if (raised)
			    awDodgeChainLastVisited->dodgeChainNext = dw;
			else
			    awDodgeChainLastVisited->dodgeChainPrev = dw;
		    }
		    else if (raised) // mark chain start
		    {
			aw->dodgeChainStart = dw;
		    }
		    if (raised)
		    {
			adw->dodgeChainPrev = wDodgeChainLastVisited;
			adw->dodgeChainNext = NULL;
		    }
		    else
		    {
			adw->dodgeChainPrev = NULL;
			adw->dodgeChainNext = wDodgeChainLastVisited;
		    }

		    // Find direction (left, right, up, down)
		    // that minimizes dodge amount

		    // Dodge amount (dodge shadows as well)

		    int dodgeAmount[4];

		    int i;
		    for (i = 0; i < 4; i++)
			dodgeAmount[i] = DODGE_AMOUNT(w, dw, i);

		    int amountMin = abs(dodgeAmount[0]);
		    int iMin = 0;
		    for (i=1; i<4; i++)
		    {
			int absAmount = abs(dodgeAmount[i]);
			if (absAmount < amountMin)
			{
			    amountMin = absAmount;
			    iMin = i;
			}
		    }
		    adw->dodgeMaxAmount = dodgeAmount[iMin];
		    adw->dodgeDirection = iMin;

		    wDodgeChainLastVisited = dw;

		    // Reset back to 0 for the next dodge calculation
		    adw->dodgeOrder = 0;
		}
		if (aw->isDodgeSubject)
		    aw->dodgeMaxAmount = 0;

		// if subject is being lowered,
		// point chain-start to the topmost doding window
		if (!raised)
		{
		    aw->dodgeChainStart = wDodgeChainLastVisited;
		}

		aw->animTotalTime =
		    maxTransformTotalProgress * duration;
	    }
	}

	if (!animEnsureModel(w))
	{
	    postAnimationCleanup(w, TRUE);
	    return;
	}

	animActivateEvent(s, TRUE);

	if (chosenEffect != AnimEffectDodge)
	    aw->animTotalTime = duration;
	aw->animRemainingTime = aw->animTotalTime;

	// Store coords in this viewport to omit 3d effect
	// painting in other viewports
	aw->lastKnownCoords.x = w->attrib.x;
	aw->lastKnownCoords.y = w->attrib.y;

	damagePendingOnScreen (s);
    }
}

// returns whether this window is relevant for fade focus
static Bool
relevantForFadeFocus(CompWindow *nw)
{
    if (!((nw->wmType &
	   // these two are to be used as "host" windows
	   // to host the painting of windows being focused
	   // at a stacking order lower than them
	   (CompWindowTypeDockMask | CompWindowTypeSplashMask)) ||
	  nw->wmType == CompWindowTypeNormalMask ||
	  nw->wmType == CompWindowTypeDialogMask ||
	  nw->wmType == CompWindowTypeUtilMask ||
	  nw->wmType == CompWindowTypeUnknownMask))
    {
	return FALSE;
    }
    return isWinVisible(nw);
}

static Bool
restackInfoStillGood(CompScreen *s, RestackInfo *restackInfo)
{
    Bool wStartGood = FALSE;
    Bool wEndGood = FALSE;
    Bool wOldAboveGood = FALSE;
    Bool wRestackedGood = FALSE;

    CompWindow *w;
    for (w = s->windows; w; w = w->next)
    {
	if (restackInfo->wStart == w && isWinVisible(w))
	    wStartGood = TRUE;
	if (restackInfo->wEnd == w && isWinVisible(w))
	    wEndGood = TRUE;
	if (restackInfo->wRestacked == w && isWinVisible(w))
	    wRestackedGood = TRUE;
	if (restackInfo->wOldAbove == w && isWinVisible(w))
	    wOldAboveGood = TRUE;
    }
    return (wStartGood && wEndGood && wOldAboveGood && wRestackedGood);
}

static void animPreparePaintScreen(CompScreen * s, int msSinceLastPaint)
{
    CompWindow *w;

    ANIM_SCREEN(s);

    // Check and update "switcher post wait" counter
    if (switcherPostWait > 0)
    {
	switcherPostWait++;
	if (switcherPostWait > 4) // wait over
	    switcherPostWait = 0;
    }
    //if (as->focusEffect == AnimEffectFocusFade ||
    //as->focusEffect == AnimEffectDodge)
    {
	if (as->aWinWasRestackedJustNow)
	{
	    // do in reverse order so that focus-fading chains are handled
	    // properly
	    for (w = s->reverseWindows; w; w = w->prev)
	    {
		ANIM_WINDOW(w);
		if (aw->restackInfo)
		{
		    if (aw->curWindowEvent != WindowEventNone ||
			otherPluginsActive(as) ||
			// Don't animate with stale restack info
			!restackInfoStillGood(s, aw->restackInfo))
		    {
			free(aw->restackInfo);
			aw->restackInfo = NULL;
			continue;
		    }

		    // Find the first window at a higher stacking order than w
		    CompWindow *nw;
		    for (nw = w->next; nw; nw = nw->next)
		    {
			if (relevantForFadeFocus(nw))
			    break;
		    }

		    // If w is being lowered, there has to be a window
		    // at a higher stacking position than w (like a panel)
		    // which this w's copy can be painted before.
		    // Otherwise the animation will only show w fading in
		    // rather than 2 copies of it cross-fading.
		    if (!aw->restackInfo->raised && !nw)
		    {
			// Free unnecessary restackInfo
			free(aw->restackInfo);
			aw->restackInfo = NULL;
			continue;
		    }

		    // Check if above window is focus-fading too.
		    // (like a dialog of an app. window)
		    // If so, focus-fade this together with the one above
		    // (link to it)
		    if (nw)
		    {
			AnimWindow *awNext = GET_ANIM_WINDOW(nw, as);
			if (awNext && awNext->winThisIsPaintedBefore)
			{
			    awNext->moreToBePaintedPrev = w;
			    aw->moreToBePaintedNext = nw;
			    aw->restackInfo->wOldAbove =
				awNext->winThisIsPaintedBefore;
			}
		    }
		    initiateFocusAnimation(w);
		}
	    }
	    //if (as->focusEffect == AnimEffectDodge)
	    {
		for (w = s->reverseWindows; w; w = w->prev)
		{
		    ANIM_WINDOW(w);

		    if (!aw->isDodgeSubject)
			continue;
		    Bool dodgersAreOnlySubjects = TRUE;
		    CompWindow *dw;
		    AnimWindow *adw;
		    for (dw = aw->dodgeChainStart; dw; dw = adw->dodgeChainNext)
		    {
			adw = GET_ANIM_WINDOW(dw, as);
			if (!adw)
			    break;
			if (!adw->isDodgeSubject)
			    dodgersAreOnlySubjects = FALSE;
		    }
		    if (dodgersAreOnlySubjects)
			aw->skipPostPrepareScreen = TRUE;
		}
	    }
	}
    }

    if (as->animInProgress)
    {
	AnimWindow *aw;
	Bool animStillInProgress = FALSE;

	for (w = s->windows; w; w = w->next)
	{
	    aw = GET_ANIM_WINDOW(w, as);

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

		for (i = 0; i < aw->numPs; i++)
		{
		    if (aw->ps[i].active)
		    {
			updateParticles(&aw->ps[i], msSinceLastPaint);
			animStillInProgress = TRUE;
		    }
		}
	    }

	    if (aw->animRemainingTime > 0)
	    {
		if (!aw->animInitialized)	// if animation is just starting
		{
		    aw->deceleratingMotion =
			animEffectProperties[aw->curAnimEffect].
			animStepPolygonFunc ==
			polygonsDeceleratingAnimStepPolygon;

		    if (playingPolygonEffect(as, aw))
		    {
			// Allocate polygon set if null
			if (!aw->polygonSet)
			{
			    aw->polygonSet = calloc(1, sizeof(PolygonSet));
			}
			if (!aw->polygonSet)
			{
			    compLogMessage (w->screen->display, 
					    "animation", CompLogLevelError,
					    "Not enough memory");
			    return;
			}
			aw->polygonSet->allFadeDuration = -1.0f;
		    }
		}

		// if 3d polygon fx
		if (playingPolygonEffect(as, aw))
		{
		    aw->nClipsPassed = 0;
		    aw->clipsUpdated = FALSE;
		}

		// If just starting, call fx init func.
		if (!aw->animInitialized &&
		    animEffectProperties[aw->curAnimEffect].initFunc)
		{
		    animEffectProperties[aw->curAnimEffect].initFunc(s, w);
		}

		if (aw->model)
		{
		    if (aw->model->winWidth != WIN_W(w) ||
			aw->model->winHeight != WIN_H(w))
		    {
			// model needs update
			// re-create model
			if (!animEnsureModel (w))
			    continue;	// skip this window
		    }
		}

		if (animEffectProperties[aw->curAnimEffect].updateBBFunc)
		{
		    copyResetBB (aw);

		    if (!aw->animInitialized &&
			(aw->curWindowEvent == WindowEventClose ||
			 aw->curWindowEvent == WindowEventMinimize ||
			 aw->curWindowEvent == WindowEventShade ||
			 ((aw->curWindowEvent == WindowEventFocus ||
			   // for dodging windows
			   aw->curAnimEffect == AnimEffectDodge) &&
			  !aw->isDodgeSubject)))
			updateBBWindow (NULL, w);
		}
		aw->animInitialized = TRUE;

		if (animEffectProperties[aw->curAnimEffect].animStepFunc)
		    animEffectProperties[aw->curAnimEffect].animStepFunc
			(s, w, msSinceLastPaint);

		if (animEffectProperties[aw->curAnimEffect].updateBBFunc)
		{
		    int i;
		    for (i = 0; i < s->nOutputDev; i++)
			animEffectProperties[aw->curAnimEffect].
			    updateBBFunc (&s->outputDev[i], w);

		    if (!(s->damageMask & COMP_SCREEN_DAMAGE_ALL_MASK))
			damageBoundingBox (w);
		}

		if (aw->animRemainingTime <= 0)
		{
		    // Animation done
		    postAnimationCleanup(w, TRUE);
		}
		animStillInProgress |= (aw->animRemainingTime > 0);
	    }

	    if (aw->animRemainingTime <= 0)
	    {
		if (aw->curAnimEffect != AnimEffectNone ||
		    aw->unmapCnt > 0 || aw->destroyCnt > 0)
		{
		    postAnimationCleanup(w, TRUE);
		}
		aw->curWindowEvent = WindowEventNone;
		aw->curAnimEffect = AnimEffectNone;
	    }
	}

	for (w = s->windows; w; w = w->next)
	{
	    aw = GET_ANIM_WINDOW(w, as);
	    if (aw && animEffectProperties[aw->curAnimEffect].
		postPreparePaintScreenFunc)
	    {
		animEffectProperties[aw->curAnimEffect].
		    postPreparePaintScreenFunc(s, w);
	    }
	}

	if (!animStillInProgress)
	    animActivateEvent(s, FALSE);
    }

    UNWRAP(as, s, preparePaintScreen);
    (*s->preparePaintScreen) (s, msSinceLastPaint);
    WRAP(as, s, preparePaintScreen, animPreparePaintScreen);
}

static void animDonePaintScreen(CompScreen * s)
{
    ANIM_SCREEN(s);

    if (as->animInProgress)
	damagePendingOnScreen (s);

    UNWRAP(as, s, donePaintScreen);
    (*s->donePaintScreen) (s);
    WRAP(as, s, donePaintScreen, animDonePaintScreen);
}

static void
animAddWindowGeometry(CompWindow * w,
		      CompMatrix * matrix,
		      int nMatrix, Region region, Region clip)
{
    ANIM_WINDOW(w);
    ANIM_SCREEN(w->screen);

    // if model is lost during animation (e.g. when plugin just reloaded)
    /*if (aw->animRemainingTime > 0 && !aw->model)
      {
      aw->animRemainingTime = 0;
      postAnimationCleanup(w, TRUE);
      }*/
    // if window is being animated
    if (aw->animRemainingTime > 0 && aw->model &&
	!(animEffectProperties[aw->curAnimEffect].letOthersDrawGeoms &&
	  animEffectProperties[aw->curAnimEffect].letOthersDrawGeoms
	  (w->screen, w)))
    {
	BoxPtr pClip;
	int nClip;
	int nVertices, nIndices;
	GLushort *i;
	GLfloat *v;
	int x1, y1, x2, y2;
	float width, height;
	float winContentsY, winContentsHeight;
	float deformedX, deformedY;
	int nVertX, nVertY, wx, wy;
	int vSize, it;
	float gridW, gridH, x, y;
	Bool rect = TRUE;
	Bool useTextureQ = TRUE;
	Model *model = aw->model;
	Region awRegion = NULL;

	// Use Q texture coordinate to avoid jagged-looking quads
	// http://www.r3.nu/~cass/qcoord/
	if (animEffectProperties[aw->curAnimEffect].dontUseQTexCoord)
	    useTextureQ = FALSE;

	if (aw->useDrawRegion)
	{
	    awRegion = XCreateRegion();
	    XOffsetRegion(aw->drawRegion, WIN_X(w), WIN_Y(w));
	    XIntersectRegion(region, aw->drawRegion, awRegion);
	    XOffsetRegion(aw->drawRegion, -WIN_X(w), -WIN_Y(w));
	    nClip = awRegion->numRects;
	    pClip = awRegion->rects;
	}
	else
	{
	    nClip = region->numRects;
	    pClip = region->rects;
	}

	if (nClip == 0)			// nothing to do
	{
	    if (awRegion)
		XDestroyRegion(awRegion);
	    return;
	}

	for (it = 0; it < nMatrix; it++)
	{
	    if (matrix[it].xy != 0.0f || matrix[it].yx != 0.0f)
	    {
		rect = FALSE;
		break;
	    }
	}

	w->drawWindowGeometry = animDrawWindowGeometry;

	if (aw->polygonSet &&
	    animEffectProperties[aw->curAnimEffect].addCustomGeometryFunc)
	{
	    /*int nClip2 = nClip;
	      BoxPtr pClip2 = pClip;

	      // For each clip passed to this function
	      for (; nClip2--; pClip2++)
	      {
	      x1 = pClip2->x1;
	      y1 = pClip2->y1;
	      x2 = pClip2->x2;
	      y2 = pClip2->y2;

	      printf("x1: %4d, y1: %4d, x2: %4d, y2: %4d", x1, y1, x2, y2);
	      printf("\tm: %5.2f %5.2f %5.2f %5.2f %5.2f %5.2f\n",
	      matrix[0].xx, matrix[0].xy, matrix[0].yx, matrix[0].yy,
	      matrix[0].x0, matrix[0].y0);
	      } */
	    if (nMatrix == 0)
		return;
	    animEffectProperties[aw->curAnimEffect].
		addCustomGeometryFunc(w->screen, w, nClip, pClip,
				      nMatrix, matrix);

	    // If addGeometryFunc exists, it is expected to do everthing
	    // to add geometries (instead of the rest of this function).

	    if (w->vCount == 0)	// if there is no vertex
	    {
		// put a dummy quad in vertices and indices
		if (4 > w->indexSize)
		{
		    if (!moreWindowIndices(w, 4))
			return;
		}
		if (4 > w->vertexSize)
		{
		    if (!moreWindowVertices(w, 4))
			return;
		}
		w->vCount = 4;
		w->indexCount = 4;
		w->texUnits = 1;
		memset(w->vertices, 0, sizeof(GLfloat) * 4);
		memset(w->indices, 0, sizeof(GLushort) * 4);
	    }
	    return;				// We're done here.
	}

	// window coordinates and size
	wx = WIN_X(w);
	wy = WIN_Y(w);
	width = WIN_W(w);
	height = WIN_H(w);

	// to be used if event is shade/unshade
	winContentsY = w->attrib.y;
	winContentsHeight = w->height;

	w->texUnits = nMatrix;

	if (w->vCount == 0)
	{
	    // reset
	    w->indexCount = 0;
	    w->texCoordSize = 4;
	}
	vSize = 2 + w->texUnits * w->texCoordSize;

	nVertices = w->vCount;
	nIndices = w->indexCount;

	v = w->vertices + (nVertices * vSize);
	i = w->indices + nIndices;

	// For each clip passed to this function
	for (; nClip--; pClip++)
	{
	    x1 = pClip->x1;
	    y1 = pClip->y1;
	    x2 = pClip->x2;
	    y2 = pClip->y2;
	    /*
	      printf("x1: %4d, y1: %4d, x2: %4d, y2: %4d", x1, y1, x2, y2);
	      printf("\tm: %5.2f %5.2f %5.2f %5.2f %5.2f %5.2f\n",
	      matrix[0].xx, matrix[0].xy, matrix[0].yx, matrix[0].yy,
	      matrix[0].x0, matrix[0].y0);
	    */
	    gridW = (float)width / (model->gridWidth - 1);

	    if (aw->curWindowEvent == WindowEventShade ||
		aw->curWindowEvent == WindowEventUnshade)
	    {
		if (y1 < w->attrib.y)	// if at top part
		{
		    gridH = model->topHeight;
		}
		else if (y2 > w->attrib.y + w->height)	// if at bottom
		{
		    gridH = model->bottomHeight;
		}
		else			// in window contents (only in Y coords)
		{
		    float winContentsHeight =
			height - model->topHeight - model->bottomHeight;
		    gridH = winContentsHeight / (model->gridHeight - 3);
		}
	    }
	    else
		gridH = (float)height / (model->gridHeight - 1);

	    // nVertX, nVertY: number of vertices for this clip in x and y dimensions
	    // + 2 to avoid running short of vertices in some cases
	    nVertX = ceil((x2 - x1) / gridW) + 2;
	    nVertY = (gridH ? ceil((y2 - y1) / gridH) : 0) + 2;

	    // Allocate 4 indices for each quad
	    int newIndexSize = nIndices + ((nVertX - 1) * (nVertY - 1) * 4);

	    if (newIndexSize > w->indexSize)
	    {
		if (!moreWindowIndices(w, newIndexSize))
		    return;

		i = w->indices + nIndices;
	    }
	    // Assign quad vertices to indices
	    int jx, jy;

	    for (jy = 0; jy < nVertY - 1; jy++)
	    {
		for (jx = 0; jx < nVertX - 1; jx++)
		{
		    *i++ = nVertices + nVertX * (2 * jy + 1) + jx;
		    *i++ = nVertices + nVertX * (2 * jy + 1) + jx + 1;
		    *i++ = nVertices + nVertX * 2 * jy + jx + 1;
		    *i++ = nVertices + nVertX * 2 * jy + jx;

		    nIndices += 4;
		}
	    }

	    // Allocate vertices
	    int newVertexSize =
		(nVertices + nVertX * (2 * nVertY - 2)) * vSize;
	    if (newVertexSize > w->vertexSize)
	    {
		if (!moreWindowVertices(w, newVertexSize))
		    return;

		v = w->vertices + (nVertices * vSize);
	    }

	    float rowTexCoordQ = 1;
	    float prevRowCellWidth = 0;	// this initial value won't be used
	    float rowCellWidth = 0;

	    // For each vertex
	    for (jy = 0, y = y1; jy < nVertY; jy++)
	    {
		if (y > y2)
		    y = y2;

		// Do calculations for y here to avoid repeating
		// them unnecessarily in the x loop

		float topiyFloat;
		Bool applyOffsets = TRUE;

		if (aw->curWindowEvent == WindowEventShade
		    || aw->curWindowEvent == WindowEventUnshade)
		{
		    if (y1 < w->attrib.y)	// if at top part
		    {
			topiyFloat = (y - WIN_Y(w)) / model->topHeight;
			topiyFloat = MIN(topiyFloat, 0.999);	// avoid 1.0
			applyOffsets = FALSE;
		    }
		    else if (y2 > w->attrib.y + w->height)	// if at bottom
		    {
			topiyFloat = (model->gridHeight - 2) +
			    (model->bottomHeight ? (y - winContentsY -
						    winContentsHeight) /
			     model->bottomHeight : 0);
			applyOffsets = FALSE;
		    }
		    else		// in window contents (only in Y coords)
		    {
			topiyFloat = (model->gridHeight - 3) * 
			    (y - winContentsY) / winContentsHeight + 1;
		    }
		}
		else
		{
		    topiyFloat = (model->gridHeight - 1) * (y - wy) / height;
		}
		// topiy should be at most (model->gridHeight - 2)
		int topiy = (int)(topiyFloat + 1e-4);

		if (topiy == model->gridHeight - 1)
		    topiy--;
		int bottomiy = topiy + 1;
		float iny = topiyFloat - topiy;

		// End of calculations for y

		for (jx = 0, x = x1; jx < nVertX; jx++)
		{
		    if (x > x2)
			x = x2;

		    // find containing grid cell (leftix rightix) x (topiy bottomiy)
		    float leftixFloat =
			(model->gridWidth - 1) * (x - wx) / width;
		    int leftix = (int)(leftixFloat + 1e-4);

		    if (leftix == model->gridWidth - 1)
			leftix--;
		    int rightix = leftix + 1;

		    // Objects that are at top, bottom, left, right corners of quad
		    Object *objToTopLeft =
			&(model->objects[topiy * model->gridWidth + leftix]);
		    Object *objToTopRight =
			&(model->objects[topiy * model->gridWidth + rightix]);
		    Object *objToBottomLeft =
			&(model->objects[bottomiy * model->gridWidth + leftix]);
		    Object *objToBottomRight =
			&(model->objects[bottomiy * model->gridWidth + rightix]);

		    // find position in cell by taking remainder of flooring
		    float inx = leftixFloat - leftix;

		    // Interpolate to find deformed coordinates

		    float hor1x = (1 - inx) *
			objToTopLeft->position.x +
			inx * objToTopRight->position.x;
		    float hor1y = (1 - inx) *
			objToTopLeft->position.y +
			inx * objToTopRight->position.y;
		    float hor2x = (1 - inx) *
			objToBottomLeft->position.x +
			inx * objToBottomRight->position.x;
		    float hor2y = (1 - inx) *
			objToBottomLeft->position.y +
			inx * objToBottomRight->position.y;

		    deformedX = (1 - iny) * hor1x + iny * hor2x;
		    deformedY = (1 - iny) * hor1y + iny * hor2y;

		    if (useTextureQ)
		    {
			if (jx == 1)
			    rowCellWidth = deformedX - v[-2];

			if (jy > 0 && jx == 1)	// do only once per row for all rows except row 0
			{
			    rowTexCoordQ = (rowCellWidth / prevRowCellWidth);

			    v[-3] = rowTexCoordQ;	// update first column
			    v[-6] *= rowTexCoordQ;	// (since we didn't know rowTexCoordQ before)
			    v[-5] *= rowTexCoordQ;
			}
		    }
		    if (rect)
		    {
			for (it = 0; it < nMatrix; it++)
			{
			    float offsetY = 0;

			    if (applyOffsets && y < y2)
				offsetY = objToTopLeft->offsetTexCoordForQuadAfter.y;

			    *v++ = COMP_TEX_COORD_X(&matrix[it], x);
			    *v++ = COMP_TEX_COORD_Y(&matrix[it], y + offsetY);
			    *v++ = 0;
			    if (useTextureQ)
			    {
				*v++ = rowTexCoordQ;	// Q texture coordinate

				if (0 < jy && jy < nVertY - 1)
				{
				    // copy first 3 texture coords to duplicate row
				    memcpy(v - 4 + nVertX * vSize, 
					   v - 4, 3 * sizeof(GLfloat));
				    *(v - 1 + nVertX * vSize) = 1;	// Q texture coordinate
				}
				if (applyOffsets &&	/*0 < jy && */
				    objToTopLeft->
				    offsetTexCoordForQuadBefore.y != 0)
				{
				    // After copying to next row, update texture y coord
				    // by following object's offset
				    offsetY = objToTopLeft-> offsetTexCoordForQuadBefore.y;
				    v[-3] = COMP_TEX_COORD_Y(&matrix[it], y + offsetY);
				}
				if (jx > 0)	// since column 0 is updated when jx == 1
				{
				    v[-4] *= rowTexCoordQ;
				    v[-3] *= rowTexCoordQ;
				}
			    }
			    else
			    {
				*v++ = 1;

				if (0 < jy && jy < nVertY - 1)
				{
				    // copy first 3 texture coords to duplicate row
				    memcpy(v - 4 + nVertX * vSize, 
					   v - 4, 3 * sizeof(GLfloat));

				    *(v - 1 + nVertX * vSize) = 1;	// Q texture coordinate
				}
				if (applyOffsets && 
				    objToTopLeft->offsetTexCoordForQuadBefore.y != 0)
				{
				    // After copying to next row, update texture y coord
				    // by following object's offset
				    offsetY = objToTopLeft->offsetTexCoordForQuadBefore.y;
				    v[-3] = COMP_TEX_COORD_Y(&matrix[it], y + offsetY);
				}
			    }
			}
		    }
		    else
		    {
			for (it = 0; it < nMatrix; it++)
			{
			    float offsetY = 0;

			    if (applyOffsets && y < y2)
			    {
				// FIXME:
				// the correct value below doesn't work for some reason
				offsetY = 0;
				//		objToTopLeft->
				//		offsetTexCoordForQuadAfter.y;
			    }

			    *v++ = COMP_TEX_COORD_XY(&matrix[it], x, y + offsetY);
			    *v++ = COMP_TEX_COORD_YX(&matrix[it], x, y + offsetY);
			    *v++ = 0;
			    if (useTextureQ)
			    {
				*v++ = rowTexCoordQ;	// Q texture coordinate

				if (0 < jy && jy < nVertY - 1)
				{
				    // copy first 3 texture coords to duplicate row
				    memcpy(v - 4 + nVertX * vSize, 
					   v - 4, 3 * sizeof(GLfloat));
				    *(v - 1 + nVertX * vSize) = 1;	// Q texture coordinate
				}
				if (applyOffsets && 
				    objToTopLeft->offsetTexCoordForQuadBefore.y != 0)
				{
				    // After copying to next row, update texture y coord
				    // by following object's offset
				    offsetY = objToTopLeft->offsetTexCoordForQuadBefore.y;
				    v[-4] = COMP_TEX_COORD_XY(&matrix[it], x,
							      y + offsetY);
				    v[-3] = COMP_TEX_COORD_YX(&matrix[it], x,
							      y + offsetY);
				}
				if (jx > 0)	// column t should be updated when jx is t+1
				{
				    v[-4] *= rowTexCoordQ;
				    v[-3] *= rowTexCoordQ;
				}
			    }
			    else
			    {
				*v++ = 1;

				if (0 < jy && jy < nVertY - 1)
				{
				    // copy first 3 texture coords to duplicate row
				    memcpy(v - 4 + nVertX * vSize, 
					   v - 4, 3 * sizeof(GLfloat));
				    *(v - 1 + nVertX * vSize) = 1;	// Q texture coordinate
				}
				if (applyOffsets && 
				    objToTopLeft->offsetTexCoordForQuadBefore.y != 0)
				{
				    // After copying to next row, update texture y coord
				    // by following object's offset
				    offsetY =
					objToTopLeft->
					offsetTexCoordForQuadBefore.y;
				    v[-4] = COMP_TEX_COORD_XY(&matrix[it], x,
							      y + offsetY);
				    v[-3] = COMP_TEX_COORD_YX(&matrix[it], x,
							      y + offsetY);
				}
			    }
			}
		    }
		    *v++ = deformedX;
		    *v++ = deformedY;

		    if (0 < jy && jy < nVertY - 1)
			memcpy(v - 2 + nVertX * vSize, v - 2, 2 * sizeof(GLfloat));

		    nVertices++;

		    // increment x properly (so that coordinates fall on grid intersections)
		    x = rightix * gridW + wx;
		}
		if (useTextureQ)
		    prevRowCellWidth = rowCellWidth;

		if (0 < jy && jy < nVertY - 1)
		{
		    v += nVertX * vSize;	// skip the duplicate row
		    nVertices += nVertX;
		}
		// increment y properly (so that coordinates fall on grid intersections)
		if (aw->curWindowEvent == WindowEventShade
		    || aw->curWindowEvent == WindowEventUnshade)
		{
		    y += gridH;
		}
		else
		{
		    y = bottomiy * gridH + wy;
		}
	    }
	}
	w->vCount = nVertices;
	w->indexCount = nIndices;
	if (awRegion)
	{
	    XDestroyRegion(awRegion);
	    awRegion = NULL;
	}
    }
    else
    {
	UNWRAP(as, w->screen, addWindowGeometry);
	(*w->screen->addWindowGeometry) (w, matrix, nMatrix, region, clip);
	WRAP(as, w->screen, addWindowGeometry, animAddWindowGeometry);
    }
}

static void
animDrawWindowTexture(CompWindow * w, CompTexture * texture,
		      const FragmentAttrib *attrib,
		      unsigned int mask)
{
    ANIM_WINDOW(w);
    ANIM_SCREEN(w->screen);

    if (aw->animRemainingTime > 0)	// if animation in progress, store texture
    {
	aw->curTexture = texture;
	aw->curPaintAttrib = *attrib;
    }

    UNWRAP(as, w->screen, drawWindowTexture);
    (*w->screen->drawWindowTexture) (w, texture, attrib, mask);
    WRAP(as, w->screen, drawWindowTexture, animDrawWindowTexture);
}

void
animDrawWindowGeometry(CompWindow * w)
{
    ANIM_WINDOW(w);

    aw->nDrawGeometryCalls++;

    ANIM_SCREEN(w->screen);

    if (playingPolygonEffect(as, aw) &&
	animEffectProperties[aw->curAnimEffect].drawCustomGeometryFunc)
    {
	animEffectProperties[aw->curAnimEffect].drawCustomGeometryFunc (w->screen, w);
	return;
    }
    int texUnit = w->texUnits;
    int currentTexUnit = 0;
    int stride = 2 + texUnit * w->texCoordSize;
    GLfloat *vertices = w->vertices + (stride - 2);

    stride *= sizeof(GLfloat);

    glVertexPointer(2, GL_FLOAT, stride, vertices);

    while (texUnit--)
    {
	if (texUnit != currentTexUnit)
	{
	    w->screen->clientActiveTexture(GL_TEXTURE0_ARB + texUnit);
	    glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	    currentTexUnit = texUnit;
	}
	vertices -= w->texCoordSize;
	glTexCoordPointer(w->texCoordSize, GL_FLOAT, stride, vertices);
    }

    glDrawElements(GL_QUADS, w->indexCount, GL_UNSIGNED_SHORT,
		   w->indices);

    // disable all texture coordinate arrays except 0
    texUnit = w->texUnits;
    if (texUnit > 1)
    {
	while (--texUnit)
	{
	    (*w->screen->clientActiveTexture) (GL_TEXTURE0_ARB + texUnit);
	    glDisableClientState(GL_TEXTURE_COORD_ARRAY);
	}

	(*w->screen->clientActiveTexture) (GL_TEXTURE0_ARB);
    }
}

static Bool
animPaintWindow(CompWindow * w,
		const WindowPaintAttrib * attrib,
		const CompTransform    *transform,
		Region region, unsigned int mask)
{
    Bool status;

    ANIM_SCREEN(w->screen);
    ANIM_WINDOW(w);

    // For Focus Fade && Focus Dodge
    if (aw->winToBePaintedBeforeThis)
    {
	CompWindow *w2 = aw->winToBePaintedBeforeThis;
	// ========= Paint w2 on host w =========

	// Go to the bottommost window in this "focus chain"
	// This chain is used to handle some cases: e.g when Find dialog
	// of an app is open, both windows should be faded when the Find
	// dialog is raised.
	CompWindow *bottommost = w2;
	CompWindow *wPrev = GET_ANIM_WINDOW(bottommost, as)->
	    moreToBePaintedPrev;
	while (wPrev)
	{
	    bottommost = wPrev;
	    wPrev = GET_ANIM_WINDOW(wPrev, as)->moreToBePaintedPrev;
	}

	// Paint each window in the chain going to the topmost
	for (w2 = bottommost; w2;
	     w2 = GET_ANIM_WINDOW(w2, as)->moreToBePaintedNext)
	{
	    AnimWindow *aw2 = GET_ANIM_WINDOW(w2, as);
	    if (!aw2)
		continue;

	    if (aw2->animTotalTime < 1e-4)
	    {
		aw2->drawnOnHostSkip = TRUE;
	    }
	    w2->indexCount = 0;
	    WindowPaintAttrib wAttrib2 = w2->lastPaint;

	    if (aw2->curAnimEffect == AnimEffectFocusFade)
		fxFocusFadeUpdateWindowAttrib2(as, w2, &wAttrib2);
	    else // if dodge
		wAttrib2.opacity = aw2->storedOpacity;

	    unsigned int mask2 = mask;
	    mask2 |= PAINT_WINDOW_TRANSFORMED_MASK;

	    aw2->nDrawGeometryCalls = 0;
	    UNWRAP(as, w2->screen, paintWindow);
	    status = (*w2->screen->paintWindow)
		(w2, &wAttrib2, transform, region, mask2);
	    WRAP(as, w2->screen, paintWindow, animPaintWindow);
	}
    }
    if (aw->drawnOnHostSkip)
    {
	aw->drawnOnHostSkip = FALSE;
	return FALSE;
    }
    if (aw->animRemainingTime > 0)
    {
	if (aw->curAnimEffect == AnimEffectDodge &&
	    aw->isDodgeSubject &&
	    aw->winThisIsPaintedBefore)
	{
	    // if aw is to be painted somewhere other than in its
	    // original stacking order, it will only be painted with
	    // the code above (but in animPaintWindow call for
	    // the window aw->winThisIsPaintedBefore), so we don't
	    // need to paint aw below
	    return FALSE;
	}

	if (aw->curWindowEvent == WindowEventFocus && otherPluginsActive(as))
	    postAnimationCleanup(w, TRUE);

	if (playingPolygonEffect(as, aw))
	{
	    if (mask & PAINT_WINDOW_TRANSFORMED_MASK)
	    {
		aw->curTextureFilter = w->screen->filter[WINDOW_TRANS_FILTER];
	    }
	    else if (mask & PAINT_WINDOW_ON_TRANSFORMED_SCREEN_MASK)
	    {
		aw->curTextureFilter = w->screen->filter[SCREEN_TRANS_FILTER];
	    }
	    else
	    {
		aw->curTextureFilter = w->screen->filter[NOTHING_TRANS_FILTER];
	    }
	}
	w->indexCount = 0;

	WindowPaintAttrib wAttrib = *attrib;
	CompTransform wTransform = *transform;

	//if (mask & PAINT_WINDOW_SOLID_MASK)
	//	return FALSE;

	// TODO: should only happen for distorting effects
	mask |= PAINT_WINDOW_TRANSFORMED_MASK;

	wAttrib.xScale = 1.0f;
	wAttrib.yScale = 1.0f;

	aw->nDrawGeometryCalls = 0;

	if (animEffectProperties[aw->curAnimEffect].updateWindowAttribFunc)
	    animEffectProperties[aw->curAnimEffect].
		updateWindowAttribFunc(as, w, &wAttrib);

	if (animEffectProperties[aw->curAnimEffect].updateWindowTransformFunc)
	    animEffectProperties[aw->curAnimEffect].
		updateWindowTransformFunc(w->screen, w, &wTransform);

	if (animEffectProperties[aw->curAnimEffect].prePaintWindowFunc)
	    animEffectProperties[aw->curAnimEffect].
		prePaintWindowFunc(w->screen, w);

	UNWRAP(as, w->screen, paintWindow);
	status = (*w->screen->paintWindow) (w, &wAttrib, &wTransform, region, mask);
	WRAP(as, w->screen, paintWindow, animPaintWindow);

	if (animEffectProperties[aw->curAnimEffect].postPaintWindowFunc)
	    animEffectProperties[aw->curAnimEffect].
		postPaintWindowFunc(w->screen, w);
    }
    else
    {
	UNWRAP(as, w->screen, paintWindow);
	status = (*w->screen->paintWindow) (w, attrib, transform, region, mask);
	WRAP(as, w->screen, paintWindow, animPaintWindow);
    }

    return status;
}

static Bool animGetWindowIconGeometry(CompWindow * w, XRectangle * rect)
{
    Atom actual;
    int result, format;
    unsigned long n, left;
    unsigned char *data;

    ANIM_DISPLAY(w->screen->display);

    result = XGetWindowProperty(w->screen->display->display, w->id,
				ad->winIconGeometryAtom,
				0L, 4L, FALSE, XA_CARDINAL, &actual,
				&format, &n, &left, &data);

    if (result == Success && n && data)
    {
	if (n == 4)
	{
	    unsigned long *geometry = (unsigned long *)data;

	    rect->x = geometry[0];
	    rect->y = geometry[1];
	    rect->width = geometry[2];
	    rect->height = geometry[3];

	    XFree(data);

	    return TRUE;
	}

	XFree(data);
    }

    return FALSE;
}

static const PluginEventInfo watchedPlugins[] =
{
    {"switcher", "activate"},
    {"scale", "activate"},
    {"group", "tabChangeActivate"},
    {"fadedesktop", "activate"},
    {"shift", "activate"},
};

static void animHandleCompizEvent(CompDisplay * d, char *pluginName,
				  char *eventName, CompOption * option, int nOption)
{
    ANIM_DISPLAY(d);

    UNWRAP (ad, d, handleCompizEvent);
    (*d->handleCompizEvent) (d, pluginName, eventName, option, nOption);
    WRAP (ad, d, handleCompizEvent, animHandleCompizEvent);

    int i;
    for (i = 0; i < NUM_WATCHED_PLUGINS; i++)
	if (strcmp(pluginName, watchedPlugins[i].pluginName) == 0)
	{
	    if (strcmp(eventName, watchedPlugins[i].activateEventName) == 0)
	    {
		Window xid = getIntOptionNamed(option, nOption, "root", 0);
		CompScreen *s = findScreenAtDisplay(d, xid);

		if (s)
		{
		    ANIM_SCREEN(s);
		    as->pluginActive[i] =
			getBoolOptionNamed(option, nOption, "active", FALSE);
		    if (i == 0)
		    {
			as->switcherWinOpeningSuppressed = FALSE;
			if (!as->pluginActive[i])
			    switcherPostWait = 1;
		    }
		}
	    }
	    break;
	}
}

static void
updateLastClientListStacking(CompScreen *s)
{
    ANIM_SCREEN(s);
    int n = s->nClientList;
    Window *clientListStacking = (Window *) (s->clientList + n) + n;

    if (as->nLastClientListStacking != n) // the number of windows has changed
    {
	Window *list;

	list = realloc (as->lastClientListStacking, sizeof (Window) * n);
	as->lastClientListStacking  = list;

	if (!list)
	{
	    as->nLastClientListStacking = 0;
	    return;
	}

	as->nLastClientListStacking = n;
    }

    // Store new client stack listing
    memcpy(as->lastClientListStacking, clientListStacking,
	   sizeof (Window) * n);
}

static Bool
windowHasUserTime(CompWindow *w)
{
    Time t;
    return getWindowUserTime (w, &t);
}

static char *
animGetWindowName (CompWindow *w)
{
    CompDisplay *d = w->screen->display;
    Atom type;
    unsigned long nItems;
    unsigned long bytesAfter;
    unsigned char *str = NULL;
    int format, result;
    char *retval;

    ANIM_DISPLAY (d);

    result = XGetWindowProperty (d->display, w->id, ad->wmNameAtom,
                                 0, LONG_MAX, FALSE, XA_STRING,
                                 &type, &format, &nItems, &bytesAfter,
                                 (unsigned char **) &str);
    if (result != Success)
        return NULL;

    if (type != XA_STRING)
    {
        XFree (str);
        return NULL;
    }
    retval = strdup ((char *) str);
    XFree (str);

    return retval;
}

static void animHandleEvent(CompDisplay * d, XEvent * event)
{
    CompWindow *w;

    ANIM_DISPLAY(d);

    switch (event->type)
    {
    case PropertyNotify:
	if (event->xproperty.atom == d->clientListStackingAtom)
	{
	    CompScreen *s = findScreenAtDisplay (d, event->xproperty.window);
	    if (s)
		updateLastClientListStacking(s);
	}
	break;
    case MapNotify:
	w = findWindowAtDisplay(d, event->xmap.window);
	if (w)
	{
	    ANIM_WINDOW(w);

	    if (aw->animRemainingTime > 0)
	    {
		aw->state = aw->newState;
	    }
	    while (aw->unmapCnt)
	    {
		unmapWindow(w);
		aw->unmapCnt--;
	    }
	}
	break;
    case DestroyNotify:
	w = findWindowAtDisplay(d, event->xunmap.window);
	if (w)
	{
	    ANIM_WINDOW(w);
	    aw->destroyCnt++;
	    w->destroyRefCnt++;
	    addWindowDamage(w);
	}
	break;
    case UnmapNotify:
	w = findWindowAtDisplay(d, event->xunmap.window);
	if (w)
	{
	    ANIM_SCREEN(w->screen);

	    if (w->pendingUnmaps && onCurrentDesktop(w))	// Normal -> Iconic
	    {
		ANIM_WINDOW(w);

		int duration = 200;
		AnimEffect chosenEffect =
		    getMatchingAnimSelection (w, WindowEventShade, &duration);

		if (w->shaded)
		{
		    // SHADE event!

		    aw->nowShaded = TRUE;

		    if (chosenEffect)
		    {
			Bool startingNew = TRUE;

			if (aw->curWindowEvent != WindowEventNone)
			{
			    if (aw->curWindowEvent != WindowEventUnshade)
			    {
				postAnimationCleanupCustom(w, TRUE, FALSE, FALSE);
			    }
			    else
			    {
				// Play the unshade effect backwards from where it left
				aw->animRemainingTime =
				    aw->animTotalTime -
				    aw->animRemainingTime;

				// avoid window remains
				if (aw->animRemainingTime <= 0)
				    aw->animRemainingTime = 1;

				startingNew = FALSE;
				if (aw->animOverrideProgressDir == 0)
				    aw->animOverrideProgressDir = 2;
				else if (aw->animOverrideProgressDir == 1)
				    aw->animOverrideProgressDir = 0;
			    }
			}

			if (startingNew)
			{
			    AnimEffect effectToBePlayed;
			    effectToBePlayed = animGetAnimEffect(
				chosenEffect,
				as->shadeRandomEffects,
				as->nShadeRandomEffects,
				as->opt[ANIM_SCREEN_OPTION_ALL_RANDOM].value.b);

			    // handle empty random effect list
			    if (effectToBePlayed == AnimEffectNone)
				break;
						
			    aw->curAnimEffect = effectToBePlayed;
			    aw->animTotalTime = duration;
			    aw->animRemainingTime = aw->animTotalTime;
			}

			animActivateEvent(w->screen, TRUE);
			aw->curWindowEvent = WindowEventShade;

			// Store coords in this viewport to omit 3d effect
			// painting in other viewports
			aw->lastKnownCoords.x = w->attrib.x;
			aw->lastKnownCoords.y = w->attrib.y;

			if (!animEnsureModel(w))
			{
			    postAnimationCleanup(w, TRUE);
			}

			aw->unmapCnt++;
			w->unmapRefCnt++;

			damagePendingOnScreen (w->screen);
		    }
		}
		else if (!w->invisible &&
			 animGetWindowIconGeometry(w, &aw->icon))
		{
		    // MINIMIZE event!

		    aw->newState = IconicState;

		    chosenEffect =
			getMatchingAnimSelection (w, WindowEventMinimize, &duration);

		    if (chosenEffect)
		    {
			Bool startingNew = TRUE;

			if (aw->curWindowEvent != WindowEventNone)
			{
			    if (aw->curWindowEvent != WindowEventUnminimize)
			    {
				postAnimationCleanupCustom(w, TRUE, FALSE, FALSE);
			    }
			    else
			    {
				// Play the unminimize effect backwards from where it left
				aw->animRemainingTime =
				    aw->animTotalTime - aw->animRemainingTime;

				// avoid window remains
				if (aw->animRemainingTime == 0)
				    aw->animRemainingTime = 1;

				startingNew = FALSE;
				if (aw->animOverrideProgressDir == 0)
				    aw->animOverrideProgressDir = 2;
				else if (aw->animOverrideProgressDir == 1)
				    aw->animOverrideProgressDir = 0;
			    }
			}

			if (startingNew)
			{
			    AnimEffect effectToBePlayed;
						
			    effectToBePlayed = animGetAnimEffect(
				chosenEffect,
				as->minimizeRandomEffects,
				as->nMinimizeRandomEffects,
				as->opt[ANIM_SCREEN_OPTION_ALL_RANDOM].value.b);

			    // handle empty random effect list
			    if (effectToBePlayed == AnimEffectNone)
				break;

			    aw->curAnimEffect = effectToBePlayed;
			    aw->animTotalTime = duration;
			    aw->animRemainingTime = aw->animTotalTime;
			}

			animActivateEvent(w->screen, TRUE);
			aw->curWindowEvent = WindowEventMinimize;

			// Store coords in this viewport to omit 3d effect
			// painting in other viewports
			aw->lastKnownCoords.x = w->attrib.x;
			aw->lastKnownCoords.y = w->attrib.y;

			if (!animEnsureModel(w))
			{
			    postAnimationCleanup(w, TRUE);
			}
			else
			{
			    /*
			      if (!animGetWindowIconGeometry(w, &aw->icon))
			      {
			      // minimize to bottom-center if there is no window list
			      aw->icon.x = w->screen->width / 2;
			      aw->icon.y = w->screen->height;
			      aw->icon.width = 100;
			      aw->icon.height = 20;
			      }
			    */

			    aw->unmapCnt++;
			    w->unmapRefCnt++;

			    damagePendingOnScreen (w->screen);
			}
		    }
		}
	    }
	    else				// X -> Withdrawn
	    {
		ANIM_WINDOW(w);

		// don't animate windows that don't have certain properties
		// like the fullscreen darkening layer of gksudo
		// or the darkening layer of x-session-manager
		if (!(w->resName || windowHasUserTime (w)) ||
		    (aw->wmName && strcasecmp (aw->wmName, "x-session-manager") == 0))
		    break;

		int duration = 200;
		AnimEffect chosenEffect =
		    getMatchingAnimSelection (w, WindowEventClose, &duration);

		// CLOSE event!

		aw->state = NormalState;
		aw->newState = WithdrawnState;

		if (chosenEffect)
		{
		    int tmpSteps = 0;

		    Bool startingNew = TRUE;

		    if (aw->animRemainingTime > 0 &&
			aw->curWindowEvent != WindowEventOpen)
		    {
			tmpSteps = aw->animRemainingTime;
			aw->animRemainingTime = 0;
		    }
		    if (aw->curWindowEvent != WindowEventNone)
		    {
			if (aw->curWindowEvent == WindowEventOpen)
			{
			    // Play the create effect backward from where it left
			    aw->animRemainingTime =
				aw->animTotalTime - aw->animRemainingTime;

			    // avoid window remains
			    if (aw->animRemainingTime <= 0)
				aw->animRemainingTime = 1;

			    startingNew = FALSE;
			    if (aw->animOverrideProgressDir == 0)
				aw->animOverrideProgressDir = 2;
			    else if (aw->animOverrideProgressDir == 1)
				aw->animOverrideProgressDir = 0;
			}
			else if (aw->curWindowEvent == WindowEventClose)
			{
			    if (aw->animOverrideProgressDir == 2)
			    {
				aw->animRemainingTime = tmpSteps;
				startingNew = FALSE;
			    }
			}
			else
			{
			    postAnimationCleanupCustom(w, TRUE, TRUE, FALSE);
			}
		    }

		    if (startingNew)
		    {
			AnimEffect effectToBePlayed;
			effectToBePlayed = animGetAnimEffect(
			    chosenEffect,
			    as->closeRandomEffects,
			    as->nCloseRandomEffects,
			    as->opt[ANIM_SCREEN_OPTION_ALL_RANDOM].value.b);

			// handle empty random effect list
			if (effectToBePlayed == AnimEffectNone)
			    break;
					
			aw->curAnimEffect = effectToBePlayed;
			aw->animTotalTime = duration;
			aw->animRemainingTime = aw->animTotalTime;
		    }
		    animActivateEvent(w->screen, TRUE);
		    aw->curWindowEvent = WindowEventClose;

		    // Store coords in this viewport to omit 3d effect
		    // painting in other viewports
		    aw->lastKnownCoords.x = w->attrib.x;
		    aw->lastKnownCoords.y = w->attrib.y;

		    if (!animEnsureModel(w))
		    {
			postAnimationCleanup(w, TRUE);
		    }
		    else if (getMousePointerXY
			     (w->screen, &aw->icon.x, &aw->icon.y))
		    {
			aw->icon.width = FAKE_ICON_SIZE;
			aw->icon.height = FAKE_ICON_SIZE;

			if (aw->curAnimEffect == AnimEffectMagicLamp)
			    aw->icon.width = 
				MAX(aw->icon.width,
				    animGetI(as, aw, ANIM_SCREEN_OPTION_MAGIC_LAMP_OPEN_START_WIDTH));
			else if (aw->curAnimEffect == AnimEffectVacuum)
			    aw->icon.width =
				MAX(aw->icon.width,
				    animGetI(as, aw, ANIM_SCREEN_OPTION_VACUUM_OPEN_START_WIDTH));

			aw->unmapCnt++;
			w->unmapRefCnt++;

			damagePendingOnScreen (w->screen);
		    }
		}
		else if (getMatchingAnimSelection (w, WindowEventOpen, &duration))
		{
		    // stop the current animation and prevent it from rewinding

		    if (aw->animRemainingTime > 0 &&
			aw->curWindowEvent != WindowEventOpen)
		    {
			aw->animRemainingTime = 0;
		    }
		    if ((aw->curWindowEvent != WindowEventNone) &&
			(aw->curWindowEvent != WindowEventClose))
		    {
			postAnimationCleanup(w, TRUE);
		    }
		    // set some properties to make sure this window will use the
		    // correct open effect the next time it's "opened"

		    animActivateEvent(w->screen, TRUE);
		    aw->curWindowEvent = WindowEventClose;

		    aw->unmapCnt++;
		    w->unmapRefCnt++;

		    damagePendingOnScreen (w->screen);
		}
	    }
	}
	break;
    case ConfigureNotify:
    {
	XConfigureEvent *ce = &event->xconfigure;
	w = findWindowAtDisplay (d, ce->window);
	if (!w)
	    break;
	if (w->prev)
	{
	    if (ce->above && ce->above == w->prev->id)
		break;
	}
	else if (ce->above == None)
	    break;
	CompScreen *s = findScreenAtDisplay (d, event->xproperty.window);
	if (!s)
	    break;

	ANIM_SCREEN(s);
	int i;
	int n = s->nClientList;
	Bool winOpenedClosed = FALSE;

	Window *clientList = (Window *) (s->clientList + n);
	Window *clientListStacking = clientList + n;

	if (n != as->nLastClientListStacking)
	    winOpenedClosed = TRUE;

	// if restacking occurred and not window open/close
	if (!winOpenedClosed)
	{
	    ANIM_WINDOW(w);
	    aw->configureNotified = TRUE;

	    // Find which window is restacked 
	    // e.g. here 8507730 was raised:
	    // 54526074 8507730 48234499 14680072 6291497
	    // 54526074 48234499 14680072 8507730 6291497
	    // compare first changed win. of row 1 with last
	    // changed win. of row 2, and vica versa
	    // the matching one is the restacked one
	    CompWindow *wRestacked = 0;
	    CompWindow *wStart = 0;
	    CompWindow *wEnd = 0;
	    CompWindow *wOldAbove = 0;
	    CompWindow *wChangeStart = 0;
	    CompWindow *wChangeEnd = 0;

	    Bool raised = FALSE;
	    int changeStart = -1;
	    int changeEnd = -1;

	    for (i = 0; i < n; i++)
	    {
		CompWindow *wi =
		    findWindowAtScreen (s, clientListStacking[i]);

		// skip if minimized (prevents flashing problem)
		if (!wi || !isWinVisible(wi))
		    continue;

		// skip if (tabbed and) hidden by Group plugin
		if (wi->state & (CompWindowStateSkipPagerMask |
				 CompWindowStateSkipTaskbarMask))
		    continue;

		if (clientListStacking[i] !=
		    as->lastClientListStacking[i])
		{
		    if (changeStart < 0)
		    {
			changeStart = i;
			wChangeStart = wi; // make use of already found w
		    }
		    else
		    {
			changeEnd = i;
			wChangeEnd = wi;
		    }
		}
		else if (changeStart >= 0) // found some change earlier
		    break;
	    }

	    // if restacking occurred
	    if (changeStart >= 0 && changeEnd >= 0)
	    {
		CompWindow *w2;

		// if we have only 2 windows changed, 
		// choose the one clicked on
		Bool preferRaised = FALSE;
		Bool onlyTwo = FALSE;

		if (wChangeEnd &&
		    clientListStacking[changeEnd] ==
		    as->lastClientListStacking[changeStart] &&
		    clientListStacking[changeStart] ==
		    as->lastClientListStacking[changeEnd])
		{
		    // Check if the window coming on top was
		    // configureNotified (clicked on)
		    AnimWindow *aw2 = GET_ANIM_WINDOW(wChangeEnd, as);
		    if (aw2->configureNotified)
		    {
			preferRaised = TRUE;
		    }
		    onlyTwo = TRUE;
		}
		// Clear all configureNotified's
		for (w2 = s->windows; w2; w2 = w2->next)
		{
		    AnimWindow *aw2 = GET_ANIM_WINDOW(w2, as);
		    aw2->configureNotified = FALSE;
		}

		if (preferRaised ||
		    (!onlyTwo &&
		     clientListStacking[changeEnd] ==
		     as->lastClientListStacking[changeStart]))
		{
		    // raised
		    raised = TRUE;
		    wRestacked = wChangeEnd;
		    wStart = wChangeStart;
		    wEnd = wRestacked;
		    wOldAbove = wStart;
		}
		else if (clientListStacking[changeStart] ==
			 as->lastClientListStacking[changeEnd] && // lowered
			 // We don't animate lowering if there is no
			 // window above this window, since this window needs
			 // to be drawn on such a "host" in animPaintWindow
			 // (at least for now).
			 changeEnd < n - 1)
		{
		    wRestacked = wChangeStart;
		    wStart = wRestacked;
		    wEnd = wChangeEnd;
		    wOldAbove = findWindowAtScreen
			(s, as->lastClientListStacking[changeEnd+1]);
		}
		for (; wOldAbove && !isWinVisible(wOldAbove);
		     wOldAbove = wOldAbove->next)
		    ;
	    }
	    if (wRestacked && wStart && wEnd && wOldAbove)
	    {
		AnimWindow *awRestacked = GET_ANIM_WINDOW(wRestacked, as);
		if (awRestacked->created)
		{
		    RestackInfo *restackInfo = calloc(1, sizeof(RestackInfo));
		    if (restackInfo)
		    {
			restackInfo->wRestacked = wRestacked;
			restackInfo->wStart = wStart;
			restackInfo->wEnd = wEnd;
			restackInfo->wOldAbove = wOldAbove;
			restackInfo->raised = raised;

			if (awRestacked->restackInfo)
			    free(awRestacked->restackInfo);

			awRestacked->restackInfo = restackInfo;
			as->aWinWasRestackedJustNow = TRUE;
		    }
		}
	    }
	}
	updateLastClientListStacking(s);
    }
    break;
    default:
	break;
    }

    UNWRAP(ad, d, handleEvent);
    (*d->handleEvent) (d, event);
    WRAP(ad, d, handleEvent, animHandleEvent);

    switch (event->type)
    {
    case PropertyNotify:
	if (event->xproperty.atom == d->winActiveAtom &&
	    d->activeWindow != ad->activeWindow)
	{
	    ad->activeWindow = d->activeWindow;
	    w = findWindowAtDisplay(d, d->activeWindow);

	    if (w)
	    {
		int duration = 200;
		AnimEffect chosenEffect =
		    getMatchingAnimSelection (w, WindowEventFocus, &duration);

		if (!(chosenEffect == AnimEffectFocusFade ||
		      chosenEffect == AnimEffectDodge))
		    initiateFocusAnimation(w);
	    }
	}
	break;
    default:
	break;
    }
}

static Bool animDamageWindowRect(CompWindow * w, Bool initial, BoxPtr rect)
{
    Bool status;

    ANIM_SCREEN(w->screen);

    if (initial)				// Unminimize or Open
    {
	ANIM_WINDOW(w);

	int duration = 200;
	AnimEffect chosenEffect;

	if (aw->state == IconicState)
	{
	    chosenEffect =
		getMatchingAnimSelection (w, WindowEventUnminimize, &duration);

	    if (!w->invisible &&
		chosenEffect &&
		!as->pluginActive[3]) // fadedesktop
	    {
		// UNMINIMIZE event!

		Bool startingNew = TRUE;

		if (aw->curWindowEvent != WindowEventNone)
		{
		    if (aw->curWindowEvent != WindowEventMinimize)
		    {
			postAnimationCleanupCustom(w, TRUE, FALSE, FALSE);
		    }
		    else
		    {
			// Play the minimize effect backwards from where it left
			aw->animRemainingTime =
			    aw->animTotalTime - aw->animRemainingTime;

			// avoid window remains
			if (aw->animRemainingTime <= 0)
			    aw->animRemainingTime = 1;

			startingNew = FALSE;
			if (aw->animOverrideProgressDir == 0)
			    aw->animOverrideProgressDir = 1;
			else if (aw->animOverrideProgressDir == 2)
			    aw->animOverrideProgressDir = 0;
		    }
		}

		Bool playEffect = TRUE;

		if (startingNew)
		{
		    AnimEffect effectToBePlayed;
		    effectToBePlayed = animGetAnimEffect(
			chosenEffect,
			as->minimizeRandomEffects,
			as->nMinimizeRandomEffects,
			as->opt[ANIM_SCREEN_OPTION_ALL_RANDOM].value.b);

		    // handle empty random effect list
		    if (effectToBePlayed == AnimEffectNone)
			playEffect = FALSE;

		    if (playEffect)
		    {
			aw->curAnimEffect = effectToBePlayed;
			aw->animTotalTime = duration;
			aw->animRemainingTime = aw->animTotalTime;
		    }
		}

		if (playEffect)
		{
		    animActivateEvent(w->screen, TRUE);
		    aw->curWindowEvent = WindowEventUnminimize;

		    // Store coords in this viewport to omit 3d effect
		    // painting in other viewports
		    aw->lastKnownCoords.x = w->attrib.x;
		    aw->lastKnownCoords.y = w->attrib.y;

		    if (animEnsureModel(w))
		    {
			if (!animGetWindowIconGeometry(w, &aw->icon))
			{
			    // minimize to bottom-center if there is no window list
			    aw->icon.x = w->screen->width / 2;
			    aw->icon.y = w->screen->height;
			    aw->icon.width = 100;
			    aw->icon.height = 20;
			}

			damagePendingOnScreen (w->screen);
		    }
		    else
		    {
			postAnimationCleanup(w, TRUE);
		    }
		}
	    }
	}
	else if (aw->nowShaded)
	{
	    chosenEffect =
		getMatchingAnimSelection (w, WindowEventUnshade, &duration);

	    // UNSHADE event!

	    aw->nowShaded = FALSE;

	    if (chosenEffect)
	    {
		Bool startingNew = TRUE;

		if (aw->curWindowEvent != WindowEventNone)
		{
		    if (aw->curWindowEvent != WindowEventShade)
		    {
			postAnimationCleanupCustom(w, TRUE, FALSE, FALSE);
		    }
		    else
		    {
			// Play the shade effect backwards from where it left
			aw->animRemainingTime =
			    aw->animTotalTime - aw->animRemainingTime;

			// avoid window remains
			if (aw->animRemainingTime <= 0)
			    aw->animRemainingTime = 1;

			startingNew = FALSE;
			if (aw->animOverrideProgressDir == 0)
			    aw->animOverrideProgressDir = 1;
			else if (aw->animOverrideProgressDir == 2)
			    aw->animOverrideProgressDir = 0;
		    }
		}

		Bool playEffect = TRUE;

		if (startingNew)
		{
		    AnimEffect effectToBePlayed;
		    effectToBePlayed = animGetAnimEffect(
			chosenEffect,
			as->shadeRandomEffects,
			as->nShadeRandomEffects,
			as->opt[ANIM_SCREEN_OPTION_ALL_RANDOM].value.b);

		    // handle empty random effect list
		    if (effectToBePlayed == AnimEffectNone)
			playEffect = FALSE;

		    if (playEffect)
		    {
			aw->curAnimEffect = effectToBePlayed;
			aw->animTotalTime = duration;
			aw->animRemainingTime = aw->animTotalTime;
		    }
		}

		if (playEffect)
		{
		    animActivateEvent(w->screen, TRUE);
		    aw->curWindowEvent = WindowEventUnshade;

		    // Store coords in this viewport to omit 3d effect
		    // painting in other viewports
		    aw->lastKnownCoords.x = w->attrib.x;
		    aw->lastKnownCoords.y = w->attrib.y;

		    if (animEnsureModel(w))
			damagePendingOnScreen (w->screen);
		    else
			postAnimationCleanup(w, TRUE);
		}
	    }
	}
	else if (!w->invisible)
	{
	    aw->created = TRUE;

	    int duration = 200;
	    AnimEffect chosenEffect =
		getMatchingAnimSelection (w, WindowEventOpen, &duration);

	    // OPEN event!

	    if (chosenEffect &&
		// don't animate windows that don't have certain properties
		// like the fullscreen darkening layer of gksudo
		(w->resName || windowHasUserTime (w)) &&
		// don't animate darkening layer of x-session-manager
		!(aw->wmName && strcasecmp (aw->wmName, "x-session-manager") == 0) &&
		// suppress switcher window
		// (1st window that opens after switcher becomes active)
		(!as->pluginActive[0] || as->switcherWinOpeningSuppressed) &&
		getMousePointerXY(w->screen, &aw->icon.x, &aw->icon.y))
	    {
		Bool startingNew = TRUE;

		if (aw->curWindowEvent != WindowEventNone)
		{
		    if (aw->curWindowEvent != WindowEventClose)
		    {
			postAnimationCleanupCustom(w, TRUE, FALSE, FALSE);
		    }
		    else
		    {
			// Play the close effect backwards from where it left
			aw->animRemainingTime =
			    aw->animTotalTime - aw->animRemainingTime;

			// avoid window remains
			if (aw->animRemainingTime == 0)
			    aw->animRemainingTime = 1;

			startingNew = FALSE;
			if (aw->animOverrideProgressDir == 0)
			    aw->animOverrideProgressDir = 1;
			else if (aw->animOverrideProgressDir == 2)
			    aw->animOverrideProgressDir = 0;
		    }
		}

		Bool playEffect = TRUE;

		if (startingNew)
		{
		    AnimEffect effectToBePlayed;
		    effectToBePlayed = animGetAnimEffect(
			chosenEffect,
			as->openRandomEffects,
			as->nOpenRandomEffects,
			as->opt[ANIM_SCREEN_OPTION_ALL_RANDOM].value.b);

		    // handle empty random effect list
		    if (effectToBePlayed == AnimEffectNone)
			playEffect = FALSE;

		    if (playEffect)
		    {
			aw->curAnimEffect = effectToBePlayed;
			aw->animTotalTime = duration;
			aw->animRemainingTime = aw->animTotalTime;
		    }
		}

		if (playEffect)
		{
		    animActivateEvent(w->screen, TRUE);
		    aw->curWindowEvent = WindowEventOpen;

		    aw->icon.width = FAKE_ICON_SIZE;
		    aw->icon.height = FAKE_ICON_SIZE;

		    if (aw->curAnimEffect == AnimEffectMagicLamp)
			aw->icon.width = 
			    MAX(aw->icon.width,
				animGetI(as, aw, ANIM_SCREEN_OPTION_MAGIC_LAMP_OPEN_START_WIDTH));
		    else if (aw->curAnimEffect == AnimEffectVacuum)
			aw->icon.width =
			    MAX(aw->icon.width,
				animGetI(as, aw, ANIM_SCREEN_OPTION_VACUUM_OPEN_START_WIDTH));

		    aw->icon.x -= aw->icon.width / 2;
		    aw->icon.y -= aw->icon.height / 2;

		    // Store coords in this viewport to omit 3d effect
		    // painting in other viewports
		    if (aw->lastKnownCoords.x != NOT_INITIALIZED)
		    {
			aw->lastKnownCoords.x = w->attrib.x;
			aw->lastKnownCoords.y = w->attrib.y;
		    }
		    if (animEnsureModel(w))
			damagePendingOnScreen (w->screen);
		    else
			postAnimationCleanup(w, TRUE);
		}
	    }
	    else if (as->pluginActive[0] && !as->switcherWinOpeningSuppressed)
	    {
		// done suppressing open animation
		as->switcherWinOpeningSuppressed = TRUE;
	    }
	}

	aw->newState = NormalState;
    }

    UNWRAP(as, w->screen, damageWindowRect);
    status = (*w->screen->damageWindowRect) (w, initial, rect);
    WRAP(as, w->screen, damageWindowRect, animDamageWindowRect);

    return status;
}

static void animWindowResizeNotify(CompWindow * w, int dx, int dy, int dwidth, int dheight)
{
    ANIM_SCREEN(w->screen);
    ANIM_WINDOW(w);

    // Don't let transient window open anim be interrupted with a resize notify
    if (!(aw->curWindowEvent == WindowEventOpen &&
	  (w->wmType &
	   (CompWindowTypeDropdownMenuMask |
	    CompWindowTypePopupMenuMask |
       	    CompWindowTypeMenuMask |
	    CompWindowTypeTooltipMask |
	    CompWindowTypeNotificationMask |
	    CompWindowTypeComboMask |
	    CompWindowTypeDndMask))))
    {
	if (aw->polygonSet && !aw->animInitialized)
	{
	    // to refresh polygon coords
	    freePolygonSet(aw);
	}

	if (aw->animRemainingTime > 0)
	{
	    aw->animRemainingTime = 0;
	    postAnimationCleanup(w, TRUE);
	}
    }

    if (aw->model)
    {
	modelInitObjects(aw->model, 
			 WIN_X(w), WIN_Y(w), 
			 WIN_W(w), WIN_H(w));
    }

    aw->state = w->state;

    UNWRAP(as, w->screen, windowResizeNotify);
    (*w->screen->windowResizeNotify) (w, dx, dy, dwidth, dheight);
    WRAP(as, w->screen, windowResizeNotify, animWindowResizeNotify);
}

static void
animWindowMoveNotify(CompWindow * w, int dx, int dy, Bool immediate)
{
    ANIM_SCREEN(w->screen);
    ANIM_WINDOW(w);

    if (!immediate)
    {
	if (!(aw->animRemainingTime > 0 &&
	      (aw->curAnimEffect == AnimEffectFocusFade ||
	       aw->curAnimEffect == AnimEffectDodge)))
	{
	    CompWindow *w2;

	    if (aw->polygonSet && !aw->animInitialized)
	    {
		// to refresh polygon coords
		freePolygonSet(aw);
	    }
	    if (aw->animRemainingTime > 0 && aw->grabbed)
	    {
		aw->animRemainingTime = 0;
		if (as->animInProgress)
		{
		    Bool animStillInProgress = FALSE;
		    for (w2 = w->screen->windows; w2; w2 = w2->next)
		    {
			AnimWindow *aw2;

			aw2 = GET_ANIM_WINDOW(w2, as);
			if (aw2->animRemainingTime > 0)
			{
			    animStillInProgress = TRUE;
			    break;
			}
		    }

		    if (!animStillInProgress)
			animActivateEvent(w->screen, FALSE);
		}
		postAnimationCleanup(w, TRUE);
	    }

	    if (aw->model)
	    {
		modelInitObjects(aw->model, WIN_X(w), WIN_Y(w), WIN_W(w),
				 WIN_H(w));
	    }
	}
    }
    else if (aw->model)
	modelMove (aw->model, dx, dy);

    UNWRAP(as, w->screen, windowMoveNotify);
    (*w->screen->windowMoveNotify) (w, dx, dy, immediate);
    WRAP(as, w->screen, windowMoveNotify, animWindowMoveNotify);
}

static void
animWindowGrabNotify(CompWindow * w,
		     int x, int y, unsigned int state, unsigned int mask)
{
    ANIM_SCREEN(w->screen);
    ANIM_WINDOW(w);

    aw->grabbed = TRUE;

    UNWRAP(as, w->screen, windowGrabNotify);
    (*w->screen->windowGrabNotify) (w, x, y, state, mask);
    WRAP(as, w->screen, windowGrabNotify, animWindowGrabNotify);
}

static void animWindowUngrabNotify(CompWindow * w)
{
    ANIM_SCREEN(w->screen);
    ANIM_WINDOW(w);

    aw->grabbed = FALSE;

    UNWRAP(as, w->screen, windowUngrabNotify);
    (*w->screen->windowUngrabNotify) (w);
    WRAP(as, w->screen, windowUngrabNotify, animWindowUngrabNotify);
}

static Bool
animPaintOutput(CompScreen * s,
		const ScreenPaintAttrib * sAttrib,
		const CompTransform    *transform,
		Region region, CompOutput *output, 
		unsigned int mask)
{
    Bool status;

    ANIM_SCREEN(s);

    if (as->animInProgress)
    {
	// Find out if an animation running now uses depth test
	Bool depthUsed = FALSE;
	CompWindow *w;
	for (w = s->windows; w; w = w->next)
	{
	    ANIM_WINDOW(w);
	    if (aw->animRemainingTime > 0 &&
		aw->polygonSet &&
		aw->polygonSet->doDepthTest)
	    {
		depthUsed = TRUE;
		break;
	    }
	}
	if (depthUsed)
	{
	    glClearDepth(1000.0f);
	    glClear(GL_DEPTH_BUFFER_BIT);
	}
	mask |= PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS_MASK;
    }

    as->output = output;

    UNWRAP(as, s, paintOutput);
    status = (*s->paintOutput) (s, sAttrib, transform, region, output, mask);
    WRAP(as, s, paintOutput, animPaintOutput);

    CompWindow *w;
    if (as->aWinWasRestackedJustNow)
    {
	as->aWinWasRestackedJustNow = FALSE;
    }
    if (as->markAllWinCreatedCountdown > 0)
    {
	if (as->markAllWinCreatedCountdown == 1)
	{
	    // Mark all windows as "created"
	    for (w = s->windows; w; w = w->next)
	    {
		ANIM_WINDOW(w);
		aw->created = TRUE;
	    }
	}
	as->markAllWinCreatedCountdown--;
    }

    return status;
}

static Bool animInitDisplay(CompPlugin * p, CompDisplay * d)
{
    AnimDisplay *ad;

    ad = calloc(1, sizeof(AnimDisplay));
    if (!ad)
	return FALSE;

    ad->screenPrivateIndex = allocateScreenPrivateIndex(d);
    if (ad->screenPrivateIndex < 0)
    {
	free(ad);
	return FALSE;
    }

    ad->wmNameAtom = XInternAtom (d->display, "WM_NAME", 0);
    ad->winIconGeometryAtom =
	XInternAtom(d->display, "_NET_WM_ICON_GEOMETRY", 0);

    WRAP(ad, d, handleEvent, animHandleEvent);
    WRAP(ad, d, handleCompizEvent, animHandleCompizEvent);

    d->privates[animDisplayPrivateIndex].ptr = ad;

    return TRUE;
}

static void animFiniDisplay(CompPlugin * p, CompDisplay * d)
{
    ANIM_DISPLAY(d);

    freeScreenPrivateIndex(d, ad->screenPrivateIndex);

    UNWRAP(ad, d, handleCompizEvent);
    UNWRAP(ad, d, handleEvent);

    free(ad);
}

static Bool animInitScreen(CompPlugin * p, CompScreen * s)
{
    AnimScreen *as;
    CompDisplay *d = s->display;

    ANIM_DISPLAY(d);

    as = calloc(1, sizeof(AnimScreen));
    if (!as)
	return FALSE;

    if (!compInitScreenOptionsFromMetadata (s,
					    &animMetadata,
					    animScreenOptionInfo,
					    as->opt,
					    ANIM_SCREEN_OPTION_NUM))
    {
	free (as);
	return FALSE;
    }

    as->windowPrivateIndex = allocateWindowPrivateIndex(s);
    if (as->windowPrivateIndex < 0)
    {
	compFiniScreenOptions (s, as->opt, ANIM_SCREEN_OPTION_NUM);
	free(as);
	return FALSE;
    }

    as->animInProgress = FALSE;

    animStoreRandomEffectList (
	&as->opt[ANIM_SCREEN_OPTION_MINIMIZE_RANDOM_EFFECTS].value,
	minimizeEffects + RANDOM_EFFECT_OFFSET,
	NUM_MINIMIZE_EFFECT - RANDOM_EFFECT_OFFSET,
	as->minimizeRandomEffects,
	&as->nMinimizeRandomEffects);

    animStoreRandomEffectList (
	&as->opt[ANIM_SCREEN_OPTION_CLOSE_RANDOM_EFFECTS].value,
	closeEffects + RANDOM_EFFECT_OFFSET,
	NUM_CLOSE_EFFECT - RANDOM_EFFECT_OFFSET,
	as->closeRandomEffects,
	&as->nCloseRandomEffects);

    animStoreRandomEffectList (
	&as->opt[ANIM_SCREEN_OPTION_OPEN_RANDOM_EFFECTS].value,
	closeEffects + RANDOM_EFFECT_OFFSET,
	NUM_CLOSE_EFFECT - RANDOM_EFFECT_OFFSET,
	as->openRandomEffects,
	&as->nOpenRandomEffects);

    animStoreRandomEffectList (
	&as->opt[ANIM_SCREEN_OPTION_SHADE_RANDOM_EFFECTS].value,
	shadeEffects + RANDOM_EFFECT_OFFSET,
	NUM_SHADE_EFFECT - RANDOM_EFFECT_OFFSET,
	as->shadeRandomEffects,
	&as->nShadeRandomEffects);

    as->eventOptionSets[WindowEventOpen] = calloc(1, sizeof(OptionSets));
    as->eventOptionSets[WindowEventClose] = calloc(1, sizeof(OptionSets));
    as->eventOptionSets[WindowEventMinimize] = calloc(1, sizeof(OptionSets));
    as->eventOptionSets[WindowEventUnminimize] =
	as->eventOptionSets[WindowEventMinimize];
    as->eventOptionSets[WindowEventFocus] = calloc(1, sizeof(OptionSets));
    as->eventOptionSets[WindowEventShade] = calloc(1, sizeof(OptionSets));
    as->eventOptionSets[WindowEventUnshade] =
	as->eventOptionSets[WindowEventShade];

    updateOptionSets
	(s, as->eventOptionSets[WindowEventOpen],
	 &as->opt[ANIM_SCREEN_OPTION_OPEN_OPTIONS].value.list);
    updateOptionSets
	(s, as->eventOptionSets[WindowEventClose],
	 &as->opt[ANIM_SCREEN_OPTION_CLOSE_OPTIONS].value.list);
    updateOptionSets
	(s, as->eventOptionSets[WindowEventMinimize],
	 &as->opt[ANIM_SCREEN_OPTION_MINIMIZE_OPTIONS].value.list);
    updateOptionSets
	(s, as->eventOptionSets[WindowEventFocus],
	 &as->opt[ANIM_SCREEN_OPTION_FOCUS_OPTIONS].value.list);
    updateOptionSets
	(s, as->eventOptionSets[WindowEventShade],
	 &as->opt[ANIM_SCREEN_OPTION_SHADE_OPTIONS].value.list);

    as->lastClientListStacking = NULL;
    as->nLastClientListStacking = 0;

    as->output = &s->fullscreenOutput;

    WRAP(as, s, preparePaintScreen, animPreparePaintScreen);
    WRAP(as, s, donePaintScreen, animDonePaintScreen);
    WRAP(as, s, paintOutput, animPaintOutput);
    WRAP(as, s, paintWindow, animPaintWindow);
    WRAP(as, s, damageWindowRect, animDamageWindowRect);
    WRAP(as, s, addWindowGeometry, animAddWindowGeometry);
    WRAP(as, s, drawWindowTexture, animDrawWindowTexture);
    WRAP(as, s, windowResizeNotify, animWindowResizeNotify);
    WRAP(as, s, windowMoveNotify, animWindowMoveNotify);
    WRAP(as, s, windowGrabNotify, animWindowGrabNotify);
    WRAP(as, s, windowUngrabNotify, animWindowUngrabNotify);

    as->markAllWinCreatedCountdown = 5; // start countdown

    s->privates[ad->screenPrivateIndex].ptr = as;

    return TRUE;
}

static void animFiniScreen(CompPlugin * p, CompScreen * s)
{
    ANIM_SCREEN(s);

    if (as->animInProgress)
	animActivateEvent(s, FALSE);

    freeWindowPrivateIndex(s, as->windowPrivateIndex);

    if (as->lastClientListStacking)
	free(as->lastClientListStacking);

    freeAllOptionSets(as->eventOptionSets);

    UNWRAP(as, s, preparePaintScreen);
    UNWRAP(as, s, donePaintScreen);
    UNWRAP(as, s, paintOutput);
    UNWRAP(as, s, paintWindow);
    UNWRAP(as, s, damageWindowRect);
    UNWRAP(as, s, addWindowGeometry);
    UNWRAP(as, s, drawWindowTexture);
    UNWRAP(as, s, windowResizeNotify);
    UNWRAP(as, s, windowMoveNotify);
    UNWRAP(as, s, windowGrabNotify);
    UNWRAP(as, s, windowUngrabNotify);

    compFiniScreenOptions (s, as->opt, ANIM_SCREEN_OPTION_NUM);

    free(as);
}

static Bool animInitWindow(CompPlugin * p, CompWindow * w)
{
    AnimWindow *aw;

    ANIM_SCREEN(w->screen);

    aw = calloc(1, sizeof(AnimWindow));
    if (!aw)
	return FALSE;

    aw->model = 0;
    aw->state = w->state;
    aw->animRemainingTime = 0;
    aw->animInitialized = FALSE;
    aw->curAnimEffect = AnimEffectNone;
    aw->curWindowEvent = WindowEventNone;
    aw->animOverrideProgressDir = 0;
    aw->curAnimSelectionRow = -1;

    w->indexCount = 0;

    aw->polygonSet = NULL;
    aw->lastKnownCoords.x = NOT_INITIALIZED;
    aw->lastKnownCoords.y = NOT_INITIALIZED;

    aw->unmapCnt = 0;
    aw->destroyCnt = 0;

    aw->grabbed = FALSE;

    aw->useDrawRegion = FALSE;
    aw->drawRegion = NULL;

    aw->BB.x1 = aw->BB.y1 = MAXSHORT;
    aw->BB.x2 = aw->BB.y2 = MINSHORT;

    if (w->shaded)
    {
	aw->state = aw->newState = NormalState;
	aw->nowShaded = TRUE;
    }
    else
    {
	aw->state = aw->newState = animGetWindowState(w);
	aw->nowShaded = FALSE;
    }

    aw->wmName = animGetWindowName(w);

    w->privates[as->windowPrivateIndex].ptr = aw;

    return TRUE;
}

static void animFiniWindow(CompPlugin * p, CompWindow * w)
{
    ANIM_WINDOW(w);

    if (aw->wmName)
	free (aw->wmName);

    postAnimationCleanup(w, FALSE);

    animFreeModel(aw);

    while (aw->unmapCnt--)
	unmapWindow(w);

    free(aw);
}

static Bool animInit(CompPlugin * p)
{
    if (!compInitPluginMetadataFromInfo (&animMetadata,
					 p->vTable->name,
					 0, 0,
					 animScreenOptionInfo,
					 ANIM_SCREEN_OPTION_NUM))
	return FALSE;

    animDisplayPrivateIndex = allocateDisplayPrivateIndex();
    if (animDisplayPrivateIndex < 0)
    {
	compFiniMetadata (&animMetadata);
	return FALSE;
    }

    compAddMetadataFromFile (&animMetadata, p->vTable->name);

    animEffectPropertiesTmp = animEffectProperties;

    return TRUE;
}

static void animFini(CompPlugin * p)
{
    freeDisplayPrivateIndex(animDisplayPrivateIndex);
    compFiniMetadata (&animMetadata);
}

static int
animGetVersion (CompPlugin *plugin,
		int	   version)
{
    return ABIVERSION;
}

static CompMetadata *
animGetMetadata (CompPlugin *plugin)
{
    return &animMetadata;
}

CompPluginVTable animVTable = {
    "animation",
    animGetVersion,
    animGetMetadata,
    animInit,
    animFini,
    animInitDisplay,
    animFiniDisplay,
    animInitScreen,
    animFiniScreen,
    animInitWindow,
    animFiniWindow,
    0,
    0,
    animGetScreenOptions,
    animSetScreenOptions,
};

CompPluginVTable *getCompPluginInfo(void)
{
    return &animVTable;
}



syntax highlighted by Code2HTML, v. 0.9.1