/*
 * Animation plugin for beryl
 *
 * animation.c
 *
 * Copyright : (C) 2006 Erkin Bahceci
 * E-mail    : cornelius@beryl-project.org
 *
 * Based on Wobbly and Minimize plugins by
 *           : David Reveman
 * E-mail    : davidr@novell.com>
 *
 * Particle system added by : (C) 2006 Dennis Kasprzyk
 * E-mail                   : onestone@beryl-project.org
 *
 * Added Beam-Up : Florencio Guimaraes
 * E-mail        : florencio@nexcorp.com.br
 *
 *
 * 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:
 *
 * - 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

#define _GNU_SOURCE
#include <string.h>
#include <stdlib.h>
#include <math.h>
#include <sys/time.h>

#ifdef USE_LIBRSVG
#include <cairo/cairo.h>
#include <cairo/cairo-xlib.h>
#include <librsvg/rsvg.h>
#include <librsvg/rsvg-cairo.h>
#endif

#include <X11/Xatom.h>
#include <X11/Xproto.h>

#include <beryl.h>

#include "animation_tex.h"

#define FAKE_ICON_SIZE 4

#define ANIM_TEXTURE_LIST_INCREMENT 5
#define ANIM_CLIP_LIST_INCREMENT 20
#define NOT_INITIALIZED -10000

#define WIN_X(w) ((w)->attrib.x - (w)->output.left)
#define WIN_Y(w) ((w)->attrib.y - (w)->output.top)
#define WIN_W(w) ((w)->width + (w)->output.left + (w)->output.right)
#define WIN_H(w) ((w)->height + (w)->output.top + (w)->output.bottom)

#define BORDER_X(w) ((w)->attrib.x - (w)->input.left)
#define BORDER_Y(w) ((w)->attrib.y - (w)->input.top)
#define BORDER_W(w) ((w)->width + (w)->input.left + (w)->input.right)
#define BORDER_H(w) ((w)->height + (w)->input.top + (w)->input.bottom)

#define RAND_FLOAT() ((float)rand() / RAND_MAX)


typedef struct _xy_pair
{
	float x, y;
} Point, Vector;

typedef struct
{
	float x1, x2, y1, y2;
} Boxf;

typedef struct _xyz_tuple
{
	float x, y, z;
} Point3d, Vector3d;

// This is intended to be a closed 3D piece of a window with convex polygon
// faces and quad-strip sides. Since decoration texture is separate from
// the window texture, it is more complicated than it would be with a single
// texture: we use glClipPlane with the rectangles (clips) to clip 3D space
// to the region falling within that clip.
// If the polygon is on an edge/corner, it also has 2D shadow quad(s)
// (to be faded out at the beginning of 3D animation if necessary).
typedef struct _PolygonObject
{
	int nVertices;				// number of total vertices (front + back)
	int nSides;					// number of sides
	GLfloat *vertices;			// Positions of vertices relative to center
	GLushort *sideIndices;		// Indices of quad strip for "sides"
	GLfloat *normals;			// Surface normals for 2+nSides faces

	Box boundingBox;			// Bound. box to test intersection with clips

	GLfloat *vertexTexCoords4Clips;
	// Tex coords for each intersecting clip and for each vertex
	// ordered as c1.v1.x, c1.v1.y, c1.v2.x, c1.v2.y, c2.v1.x, c2.v1.y, ...

	/*
	   int *vertexOnEdge;           // 1,2,3,4: W,E,N,S edge  0: not edge
	   // 5,6,7,8: NW,NE,SW,SE corner
	   // (used for shadow quad generation)
	   int nShadowQuads;
	   GLfloat *shadowVertices; // Shadow vertices positions relative to center
	   GLfloat *shadowTexCoords;    // Texture coords of shadow vertices
	 */
	// Animation effect parameters

	Point3d centerPosStart;		// Starting position of center
	float rotAngleStart;		// Starting rotation angle

	Point3d centerPos;			// Position of center
	Vector3d rotAxis;			// Rotation axis vector
	float rotAngle;				// Rotation angle
	Point3d rotAxisOffset;		// Rotation axis translate amount

	Point centerRelPos;			// Relative pos of center within the window

	Vector3d finalRelPos;		// Velocity factor for scripted movement
	float finalRotAng;			// Final rotation angle around rotAxis

	float moveStartTime;		// Movement starts at this time ([0-1] range)
	float moveDuration;			// Movement lasts this long     ([0-1] range)

	float fadeStartTime;		// Fade out starts at this time ([0,1] range)
	float fadeDuration;			// Duration of fade out         ([0,1] range)
} PolygonObject;

typedef struct _Clip4Polygons	// Rectangular clips
{								// (to hold clips passed to AddWindowGeometry)
	int id;						// clip id (what number this clip is among
	// passed clips)
	Box box;					// Coords
	Boxf boxf;					// Float coords (for small clipping adjustment)
	CompMatrix texMatrix;		// Corresponding texture coord. matrix
	int *intersectingPolygons;
	int nIntersectingPolygons;	// Clips (in PolygonSet) that intersect
	GLfloat *polygonVertexTexCoords;
	// Tex coords for each intersecting polygon and for each vertex
	// ordered as p1.v1.x, p1.v1.y, p1.v2.x, p1.v2.y, p2.v1.x, p2.v1.y, ...
} Clip4Polygons;

typedef struct _PolygonSet		// Polygon objects with same thickness
{
	int nClips;					// Rect. clips collected in AddWindowGeometries
	Clip4Polygons *clips;		// List of clips
	int clipCapacity;			// # of clips this list can hold
	int firstNondrawnClip;
	int *lastClipInGroup;		// index of the last clip in each group of clips
	// drawn in drawGeometry func.

	Bool doDepthTest;           // whether depth testing should be used in the effect
	Bool doLighting;            // whether lighting should be used in the effect
	Bool correctPerspective;    // Whether perspective look should be neutralized
	PolygonObject *polygons;	// The polygons in this set
	int nPolygons;
	float thickness;			// Window thickness (depth along z axis)
	int nTotalFrontVertices;	// Total # of polygon vertices on front faces
	float backAndSidesFadeDur;	// How long side and back faces should fade in/out
	float allFadeDuration;		// Duration of fade out at the end in [0,1] range
	// when all polygons fade out at the same time.
	// If >-1, this overrides fadeDuration in PolygonObject

	Bool includeShadows;        // include shadows in polygon
} PolygonSet;

typedef struct _WaveParam
{
	float halfWidth;
	float amp;
	float pos;
} WaveParam;

typedef enum
{
	WindowEventNone = 0,
	WindowEventMinimize,
	WindowEventUnminimize,
	WindowEventClose,
	WindowEventCreate,
	WindowEventFocus,
	WindowEventShade,
	WindowEventUnshade
} WindowEvent;

typedef struct _Object
{
	Point gridPosition;		// position on window in [0,1] range
	Point position;			// position on screen
	Point3d posRel3d;			// position relative to model center
	//						   (for 3d looking effects)

	// Texture x, y coordinates will be offset by given amounts
	// for quads that fall after and before this object in x and y directions.
	// Currently only y offset can be used.
	Point offsetTexCoordForQuadBefore;
	Point offsetTexCoordForQuadAfter;
} Object;

typedef struct _Model
{
	Object *objects;
	int numObjects;
	int gridWidth;
	int gridHeight;

	int winWidth;				// keeps win. size when model was created
	int winHeight;				//

	float remainderSteps;
	Vector scale;
	Point scaleOrigin;
	Point topLeft;
	Point bottomRight;

	int magicLampWaveCount;
	WaveParam *magicLampWaves;
	WindowEvent forWindowEvent;
	float topHeight;
	float bottomHeight;
} Model;


static void
modelInitObjects(Model * model, int x, int y, int width, int height);

static void
postAnimationCleanup(CompWindow * w, Bool resetAnimation);


#define ANIM_MAGIC_LAMP1_GRID_RES_DEFAULT  100
#define ANIM_MAGIC_LAMP1_GRID_RES_MIN      4
#define ANIM_MAGIC_LAMP1_GRID_RES_MAX      200

#define ANIM_MAGIC_LAMP1_MAX_WAVES_DEFAULT  5
#define ANIM_MAGIC_LAMP1_MAX_WAVES_MIN      3
#define ANIM_MAGIC_LAMP1_MAX_WAVES_MAX      20

#define ANIM_MAGIC_LAMP1_WAVE_AMP_MIN_DEFAULT  200
#define ANIM_MAGIC_LAMP1_WAVE_AMP_MIN_MIN      200
#define ANIM_MAGIC_LAMP1_WAVE_AMP_MIN_MAX      2000
#define ANIM_MAGIC_LAMP1_WAVE_AMP_MIN_PRECISION 5

#define ANIM_MAGIC_LAMP1_WAVE_AMP_MAX_DEFAULT  300
#define ANIM_MAGIC_LAMP1_WAVE_AMP_MAX_MIN      200
#define ANIM_MAGIC_LAMP1_WAVE_AMP_MAX_MAX      2000
#define ANIM_MAGIC_LAMP1_WAVE_AMP_MAX_PRECISION 5

#define ANIM_MAGIC_LAMP1_CREATE_START_WIDTH_DEFAULT  30
#define ANIM_MAGIC_LAMP1_CREATE_START_WIDTH_MIN      0
#define ANIM_MAGIC_LAMP1_CREATE_START_WIDTH_MAX      500

#define ANIM_MAGIC_LAMP2_GRID_RES_DEFAULT  100
#define ANIM_MAGIC_LAMP2_GRID_RES_MIN      2
#define ANIM_MAGIC_LAMP2_GRID_RES_MAX      200

#define ANIM_MAGIC_LAMP2_MAX_WAVES_DEFAULT  3
#define ANIM_MAGIC_LAMP2_MAX_WAVES_MIN      3
#define ANIM_MAGIC_LAMP2_MAX_WAVES_MAX      20

#define ANIM_MAGIC_LAMP2_WAVE_AMP_MIN_DEFAULT  200
#define ANIM_MAGIC_LAMP2_WAVE_AMP_MIN_MIN      200
#define ANIM_MAGIC_LAMP2_WAVE_AMP_MIN_MAX      2000
#define ANIM_MAGIC_LAMP2_WAVE_AMP_MIN_PRECISION 5

#define ANIM_MAGIC_LAMP2_WAVE_AMP_MAX_DEFAULT  300
#define ANIM_MAGIC_LAMP2_WAVE_AMP_MAX_MIN      200
#define ANIM_MAGIC_LAMP2_WAVE_AMP_MAX_MAX      2000
#define ANIM_MAGIC_LAMP2_WAVE_AMP_MAX_PRECISION 5

#define ANIM_MAGIC_LAMP2_CREATE_START_WIDTH_DEFAULT  30
#define ANIM_MAGIC_LAMP2_CREATE_START_WIDTH_MIN      0
#define ANIM_MAGIC_LAMP2_CREATE_START_WIDTH_MAX      500

#define ANIM_MAGIC_LAMP3_GRID_RES_DEFAULT  100
#define ANIM_MAGIC_LAMP3_GRID_RES_MIN      2
#define ANIM_MAGIC_LAMP3_GRID_RES_MAX      200

#define ANIM_MAGIC_LAMP3_MAX_WAVES_DEFAULT  0
#define ANIM_MAGIC_LAMP3_MAX_WAVES_MIN      0
#define ANIM_MAGIC_LAMP3_MAX_WAVES_MAX      20

#define ANIM_MAGIC_LAMP3_WAVE_AMP_MIN_DEFAULT  200
#define ANIM_MAGIC_LAMP3_WAVE_AMP_MIN_MIN      200
#define ANIM_MAGIC_LAMP3_WAVE_AMP_MIN_MAX      2000
#define ANIM_MAGIC_LAMP3_WAVE_AMP_MIN_PRECISION 5

#define ANIM_MAGIC_LAMP3_WAVE_AMP_MAX_DEFAULT  300
#define ANIM_MAGIC_LAMP3_WAVE_AMP_MAX_MIN      200
#define ANIM_MAGIC_LAMP3_WAVE_AMP_MAX_MAX      2000
#define ANIM_MAGIC_LAMP3_WAVE_AMP_MAX_PRECISION 5

#define ANIM_MAGIC_LAMP3_CREATE_START_WIDTH_DEFAULT  30
#define ANIM_MAGIC_LAMP3_CREATE_START_WIDTH_MIN      0
#define ANIM_MAGIC_LAMP3_CREATE_START_WIDTH_MAX      500

#define ANIM_SIDEKICK_NUM_ROTATIONS_DEFAULT      0.5
#define ANIM_SIDEKICK_NUM_ROTATIONS_MIN          0
#define ANIM_SIDEKICK_NUM_ROTATIONS_MAX          5.0
#define ANIM_SIDEKICK_NUM_ROTATIONS_PRECISION    0.01

#define ANIM_MINIMIZE_DURATION_DEFAULT   0.2
#define ANIM_MINIMIZE_DURATION_MIN       0.1
#define ANIM_MINIMIZE_DURATION_MAX       10
#define ANIM_MINIMIZE_DURATION_PRECISION 0.05

#define ANIM_UNMINIMIZE_DURATION_DEFAULT   0.2
#define ANIM_UNMINIMIZE_DURATION_MIN       0.1
#define ANIM_UNMINIMIZE_DURATION_MAX       10
#define ANIM_UNMINIMIZE_DURATION_PRECISION 0.05

#define ANIM_CREATE1_DURATION_DEFAULT   0.2
#define ANIM_CREATE1_DURATION_MIN       0.1
#define ANIM_CREATE1_DURATION_MAX       10
#define ANIM_CREATE1_DURATION_PRECISION 0.05

#define ANIM_CLOSE1_DURATION_DEFAULT   0.2
#define ANIM_CLOSE1_DURATION_MIN       0.1
#define ANIM_CLOSE1_DURATION_MAX       10
#define ANIM_CLOSE1_DURATION_PRECISION 0.05

#define ANIM_CREATE2_DURATION_DEFAULT   0.15
#define ANIM_CREATE2_DURATION_MIN       0.1
#define ANIM_CREATE2_DURATION_MAX       10
#define ANIM_CREATE2_DURATION_PRECISION 0.05

#define ANIM_CLOSE2_DURATION_DEFAULT   0.15
#define ANIM_CLOSE2_DURATION_MIN       0.1
#define ANIM_CLOSE2_DURATION_MAX       10
#define ANIM_CLOSE2_DURATION_PRECISION 0.05

#define ANIM_FOCUS_DURATION_DEFAULT   0.5
#define ANIM_FOCUS_DURATION_MIN       0.1
#define ANIM_FOCUS_DURATION_MAX       10
#define ANIM_FOCUS_DURATION_PRECISION 0.05

#define ANIM_SHADE_DURATION_DEFAULT   0.5
#define ANIM_SHADE_DURATION_MIN       0.1
#define ANIM_SHADE_DURATION_MAX       10
#define ANIM_SHADE_DURATION_PRECISION 0.05

#define ANIM_UNSHADE_DURATION_DEFAULT   0.5
#define ANIM_UNSHADE_DURATION_MIN       0.1
#define ANIM_UNSHADE_DURATION_MAX       10
#define ANIM_UNSHADE_DURATION_PRECISION 0.05

#define ANIM_FOCUS_WAVE_WIDTH_DEFAULT   0.7
#define ANIM_FOCUS_WAVE_WIDTH_MIN       0.02
#define ANIM_FOCUS_WAVE_WIDTH_MAX       3
#define ANIM_FOCUS_WAVE_WIDTH_PRECISION 0.02

#define ANIM_FOCUS_WAVE_AMP_DEFAULT   0.03
#define ANIM_FOCUS_WAVE_AMP_MIN       0
#define ANIM_FOCUS_WAVE_AMP_MAX       1
#define ANIM_FOCUS_WAVE_AMP_PRECISION 0.01

#define ANIM_CURVED_FOLD_AMP_DEFAULT   0.15
#define ANIM_CURVED_FOLD_AMP_MIN      -0.5
#define ANIM_CURVED_FOLD_AMP_MAX       0.5
#define ANIM_CURVED_FOLD_AMP_PRECISION 0.01

#define ANIM_HORIZONTAL_FOLDS_NUM_FOLDS_DEFAULT  5
#define ANIM_HORIZONTAL_FOLDS_NUM_FOLDS_MIN      1
#define ANIM_HORIZONTAL_FOLDS_NUM_FOLDS_MAX      50

#define ANIM_HORIZONTAL_FOLDS_AMP_DEFAULT   0.07
#define ANIM_HORIZONTAL_FOLDS_AMP_MIN      -0.5
#define ANIM_HORIZONTAL_FOLDS_AMP_MAX       0.5
#define ANIM_HORIZONTAL_FOLDS_AMP_PRECISION 0.01

#define ANIM_TIME_STEP_DEFAULT   10
#define ANIM_TIME_STEP_MIN       1
#define ANIM_TIME_STEP_MAX       400

#define ANIM_TIME_STEP_INTENSE_DEFAULT   30
#define ANIM_TIME_STEP_INTENSE_MIN       1
#define ANIM_TIME_STEP_INTENSE_MAX       400

#define ANIM_ROLLUP_FIXED_INTERIOR_DEFAULT  FALSE

#define ANIM_ALL_RANDOM_DEFAULT  FALSE

#define ANIM_FIRE_SIZE_DEFAULT          5.0
#define ANIM_FIRE_SIZE_MIN              0.1
#define ANIM_FIRE_SIZE_MAX              20.0
#define ANIM_FIRE_SIZE_PRECISION        0.1

#define ANIM_FIRE_SLOWDOWN_DEFAULT      0.5
#define ANIM_FIRE_SLOWDOWN_MIN          0.1
#define ANIM_FIRE_SLOWDOWN_MAX          10.0
#define ANIM_FIRE_SLOWDOWN_PRECISION    0.1

#define ANIM_FIRE_LIFE_DEFAULT          0.7
#define ANIM_FIRE_LIFE_MIN              0.1
#define ANIM_FIRE_LIFE_MAX              1.0
#define ANIM_FIRE_LIFE_PRECISION        0.1

#define ANIM_FIRE_PARTICLES_DEFAULT     1000
#define ANIM_FIRE_PARTICLES_MIN         100
#define ANIM_FIRE_PARTICLES_MAX         10000

#define ANIM_FIRE_COLOR_RED_DEFAULT     0xffff
#define ANIM_FIRE_COLOR_GREEN_DEFAULT   0x3333
#define ANIM_FIRE_COLOR_BLUE_DEFAULT    0x0555
#define ANIM_FIRE_COLOR_ALPHA_DEFAULT   0xffff

#define ANIM_FIRE_CONSTANT_SPEED_DEFAULT FALSE

#define ANIM_FIRE_SMOKE_DEFAULT         FALSE
#define ANIM_FIRE_MYSTICAL_DEFAULT         FALSE

#define ANIM_BEAMUP_SIZE_DEFAULT          8.0
#define ANIM_BEAMUP_SIZE_MIN              0.1
#define ANIM_BEAMUP_SIZE_MAX              20.0
#define ANIM_BEAMUP_SIZE_PRECISION        0.1

#define ANIM_BEAMUP_LIFE_DEFAULT          0.7
#define ANIM_BEAMUP_LIFE_MIN              0.1
#define ANIM_BEAMUP_LIFE_MAX              1.0
#define ANIM_BEAMUP_LIFE_PRECISION        0.1


#define ANIM_BEAMUP_SLOWDOWN_DEFAULT      1.0
#define ANIM_BEAMUP_SLOWDOWN_MIN          0.1
#define ANIM_BEAMUP_SLOWDOWN_MAX          10.0
#define ANIM_BEAMUP_SLOWDOWN_PRECISION    0.1

#define ANIM_BEAMUP_SPACING_DEFAULT     5
#define ANIM_BEAMUP_SPACING_MIN         1
#define ANIM_BEAMUP_SPACING_MAX         20

#define ANIM_BEAMUP_COLOR_RED_DEFAULT   0x7fff
#define ANIM_BEAMUP_COLOR_GREEN_DEFAULT 0x7fff
#define ANIM_BEAMUP_COLOR_BLUE_DEFAULT  0x7fff
#define ANIM_BEAMUP_COLOR_ALPHA_DEFAULT 0xffff


#define ANIM_GLIDE1_AWAY_POS_DEFAULT      1.0
#define ANIM_GLIDE1_AWAY_POS_MIN          -2.0
#define ANIM_GLIDE1_AWAY_POS_MAX          1.0
#define ANIM_GLIDE1_AWAY_POS_PRECISION    0.05

#define ANIM_GLIDE1_AWAY_ANGLE_DEFAULT      90.0
#define ANIM_GLIDE1_AWAY_ANGLE_MIN          -540.0
#define ANIM_GLIDE1_AWAY_ANGLE_MAX          540.0
#define ANIM_GLIDE1_AWAY_ANGLE_PRECISION    5.0

#define ANIM_GLIDE1_THICKNESS_DEFAULT      0.0
#define ANIM_GLIDE1_THICKNESS_MIN          0.0
#define ANIM_GLIDE1_THICKNESS_MAX          500.0
#define ANIM_GLIDE1_THICKNESS_PRECISION    5.0

#define ANIM_GLIDE2_AWAY_POS_DEFAULT      -0.4
#define ANIM_GLIDE2_AWAY_POS_MIN          -2.0
#define ANIM_GLIDE2_AWAY_POS_MAX          1.0
#define ANIM_GLIDE2_AWAY_POS_PRECISION    0.05

#define ANIM_GLIDE2_AWAY_ANGLE_DEFAULT      -45.0
#define ANIM_GLIDE2_AWAY_ANGLE_MIN          -540.0
#define ANIM_GLIDE2_AWAY_ANGLE_MAX          540.0
#define ANIM_GLIDE2_AWAY_ANGLE_PRECISION    5.0

#define ANIM_GLIDE2_THICKNESS_DEFAULT      0.0
#define ANIM_GLIDE2_THICKNESS_MIN          0.0
#define ANIM_GLIDE2_THICKNESS_MAX          500.0
#define ANIM_GLIDE2_THICKNESS_PRECISION    5.0


typedef enum
{
	AnimDirectionDown = 0,
	AnimDirectionUp,
	AnimDirectionLeft,
	AnimDirectionRight,
	AnimDirectionRandom,
	AnimDirectionAuto
} AnimDirection;

static char *animDirectionName[] = {
	N_("Down"),
	N_("Up"),
	N_("Left"),
	N_("Right"),
	N_("Random"),
	N_("Automatic")
};

#define ANIM_FIRE_DIRECTION_DEFAULT AnimDirectionDown
#define ANIM_DOMINO_DIRECTION_DEFAULT AnimDirectionAuto
#define ANIM_RAZR_DIRECTION_DEFAULT AnimDirectionAuto

typedef enum
{
	PostprocessDisablingNone = 0,
	PostprocessDisablingWindow,
	PostprocessDisablingScreen
} PostprocessDisabling;

static char *ppDisablingName[] = {
	N_("No disabling"),
	N_("Animated window"),
	N_("All windows")
};

typedef enum
{
	ZoomFromCenterOff = 0,
	ZoomFromCenterMin,
	ZoomFromCenterCreate,
	ZoomFromCenterOn
} ZoomFromCenter;

static char *zoomFromCenterOption[] = {
	N_("Off"),
	N_("Minimize/Unminimize Only"),
	N_("Create/Close Only"),
	N_("On")
};

#define ANIM_ZOOM_FROM_CENTER_DEFAULT 0

#define ANIM_DISABLE_PP_FX_DEFAULT PostprocessDisablingWindow

// =====================  Particle engine  =========================

typedef struct _Particle
{
	float life;					// particle life
	float fade;					// fade speed
	float width;				// particle width
	float height;				// particle height
	float w_mod;				// particle size modification during life
	float h_mod;				// particle size modification during life
	float r;					// red value
	float g;					// green value
	float b;					// blue value
	float a;					// alpha value
	float x;					// X position
	float y;					// Y position
	float z;					// Z position
	float xi;					// X direction
	float yi;					// Y direction
	float zi;					// Z direction
	float xg;					// X gravity
	float yg;					// Y gravity
	float zg;					// Z gravity
	float xo;					// orginal X position
	float yo;					// orginal Y position
	float zo;					// orginal Z position
} Particle;

typedef struct _ParticleSystem
{
	int numParticles;
	Particle *particles;
	float slowdown;
	GLuint tex;
	Bool active;
	int x, y;
	float darken;
	GLuint blendMode;

	// Moved from drawParticles to get rid of spurious malloc's
	GLfloat *vertices_cache;
	int vertex_cache_count;
	GLfloat *coords_cache;
	int coords_cache_count;
	GLfloat *colors_cache;
	int color_cache_count;
	GLfloat *dcolors_cache;
	int dcolors_cache_count;
} ParticleSystem;


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

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

	int i;

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

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

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

	int i;
	Particle *part;

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

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

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

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

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

	int numActive = 0;

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

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

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

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

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

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

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

			vertices += 12;

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

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

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

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

			coords += 8;

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

			colors += 16;

			if (ps->darken > 0)
			{

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

				dcolors += 16;
			}
		}
	}

	glEnableClientState(GL_COLOR_ARRAY);

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

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

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

	glDrawArrays(GL_QUADS, 0, numActive);

	glDisableClientState(GL_COLOR_ARRAY);

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

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

	ps->active = FALSE;

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

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

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

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

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

// =====================  END: Particle engine  =========================

static char *allEffectName[] = {
	N_("None"),
	N_("Random"),
	N_("Beam Up"),
	N_("Burn"),
	N_("Curved Fold"),
	N_("Domino (3D)"),
	N_("Dream"),
	N_("Explode (3D)"),
	N_("Fade"),
	N_("Glide 1 (3D)"),
	N_("Glide 2 (3D)"),
	N_("Horizontal Folds"),
	N_("Leaf Spread (3D)"),
	N_("Magic Lamp 1"),
	N_("Magic Lamp 2"),
	N_("Magic Lamp 3 (Vacuum)"),
	N_("Razr (3D)"),
	N_("Roll Up"),
	N_("Sidekick"),
	N_("Wave"),
	N_("Zoom")
};

typedef enum
{
	AnimEffectNone = 0,
	AnimEffectRandom,
	AnimEffectBeamUp,
	AnimEffectBurn,
	AnimEffectCurvedFold,
	AnimEffectDomino3D,
	AnimEffectDream,
	AnimEffectExplode3D,
	AnimEffectFade,
	AnimEffectGlide3D1,
	AnimEffectGlide3D2,
	AnimEffectHorizontalFolds,
	AnimEffectLeafSpread3D,
	AnimEffectMagicLamp1,
	AnimEffectMagicLamp2,
	AnimEffectMagicLamp3,
	AnimEffectRazr3D,
	AnimEffectRollUp,
	AnimEffectSidekick,
	AnimEffectWave,
	AnimEffectZoom,
	AnimEffectNum
} AnimEffect;

#define ANIM_MINIMIZE_DEFAULT   AnimEffectMagicLamp2
#define ANIM_UNMINIMIZE_DEFAULT AnimEffectMagicLamp2
#define ANIM_CLOSE1_DEFAULT     AnimEffectZoom
#define ANIM_CREATE1_DEFAULT    AnimEffectZoom
#define ANIM_CLOSE2_DEFAULT     AnimEffectFade
#define ANIM_CREATE2_DEFAULT    AnimEffectFade
#define ANIM_FOCUS_DEFAULT      AnimEffectNone
#define ANIM_SHADE_DEFAULT      AnimEffectRollUp
#define ANIM_UNSHADE_DEFAULT    AnimEffectRollUp

static char *minimizeEffectName[] = {
	N_("None"),
	N_("Random"),
	N_("Beam Up"),
	N_("Burn"),
	N_("Curved Fold"),
	N_("Domino (3D)"),
	N_("Dream"),
	N_("Explode (3D)"),
	N_("Fade"),
	N_("Glide 1 (3D)"),
	N_("Glide 2 (3D)"),
	N_("Horizontal Folds"),
	N_("Leaf Spread (3D)"),
	N_("Magic Lamp 1"),
	N_("Magic Lamp 2"),
	N_("Razr (3D)"),
	N_("Sidekick"),
	N_("Zoom")
};

static AnimEffect minimizeEffectType[] = {
	AnimEffectNone,
	AnimEffectRandom,
	AnimEffectBeamUp,
	AnimEffectBurn,
	AnimEffectCurvedFold,
	AnimEffectDomino3D,
	AnimEffectDream,
	AnimEffectExplode3D,
	AnimEffectFade,
	AnimEffectGlide3D1,
	AnimEffectGlide3D2,
	AnimEffectHorizontalFolds,
	AnimEffectLeafSpread3D,
	AnimEffectMagicLamp1,
	AnimEffectMagicLamp2,
	AnimEffectRazr3D,
	AnimEffectSidekick,
	AnimEffectZoom
};

#define NUM_MINIMIZE_EFFECT (sizeof (minimizeEffectType) / sizeof (minimizeEffectType[0]))

static char *closeEffectName[] = {
	N_("None"),
	N_("Random"),
	N_("Beam Up"),
	N_("Burn"),
	N_("Curved Fold"),
	N_("Domino (3D)"),
	N_("Dream"),
	N_("Explode (3D)"),
	N_("Fade"),
	N_("Glide 1 (3D)"),
	N_("Glide 2 (3D)"),
	N_("Horizontal Folds"),
	N_("Leaf Spread (3D)"),
	N_("Magic Lamp 1"),
	N_("Magic Lamp 2"),
	N_("Magic Lamp 3 (Vacuum)"),
	N_("Razr (3D)"),
	N_("Sidekick"),
	N_("Wave"),
	N_("Zoom")
};

static AnimEffect closeEffectType[] = {
	AnimEffectNone,
	AnimEffectRandom,
	AnimEffectBeamUp,
	AnimEffectBurn,
	AnimEffectCurvedFold,
	AnimEffectDomino3D,
	AnimEffectDream,
	AnimEffectExplode3D,
	AnimEffectFade,
	AnimEffectGlide3D1,
	AnimEffectGlide3D2,
	AnimEffectHorizontalFolds,
	AnimEffectLeafSpread3D,
	AnimEffectMagicLamp1,
	AnimEffectMagicLamp2,
	AnimEffectMagicLamp3,
	AnimEffectRazr3D,
	AnimEffectSidekick,
	AnimEffectWave,
	AnimEffectZoom
};

#define NUM_CLOSE_EFFECT (sizeof (closeEffectType) / sizeof (closeEffectType[0]))

static char *focusEffectName[] = {
	N_("None"),
	N_("Wave")
};

static AnimEffect focusEffectType[] = {
	AnimEffectNone,
	AnimEffectWave
};

#define NUM_FOCUS_EFFECT (sizeof (focusEffectType) / sizeof (focusEffectType[0]))

static char *shadeEffectName[] = {
	N_("None"),
	N_("Random"),
	N_("Curved Fold"),
	N_("Horizontal Folds"),
	N_("Roll Up")
};

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

#define NUM_SHADE_EFFECT (sizeof (shadeEffectType) / sizeof (shadeEffectType[0]))

static char *minimizeDefaultWinType[] = {
	N_("Normal"),
	N_("Dialog"),
	N_("ModalDialog"),
	N_("Utility")
};

#define N_MINIMIZE_DEFAULT_WIN_TYPE (sizeof (minimizeDefaultWinType) / sizeof (minimizeDefaultWinType[0]))

static char *close1DefaultWinType[] = {
	N_("Normal"),
	N_("Dialog"),
	N_("ModalDialog"),
	N_("Utility")
};

#define N_CLOSE1_DEFAULT_WIN_TYPE (sizeof (close1DefaultWinType) / sizeof (close1DefaultWinType[0]))

static char *close2DefaultWinType[] = {
	N_("Unknown"),
	N_("Menu"),
	N_("PopupMenu"),
	N_("DropdownMenu"),
	N_("Tooltip")
};

#define N_CLOSE2_DEFAULT_WIN_TYPE (sizeof (close2DefaultWinType) / sizeof (close2DefaultWinType[0]))

static char *focusDefaultWinType[] = {
	N_("Normal"),
	N_("Dialog"),
	N_("ModalDialog"),
	N_("Dnd")
};

#define N_FOCUS_DEFAULT_WIN_TYPE (sizeof (focusDefaultWinType) / sizeof (focusDefaultWinType[0]))

static char *shadeDefaultWinType[] = {
	N_("Normal"),
	N_("Dialog"),
	N_("ModalDialog"),
	N_("Utility")
};

#define N_SHADE_DEFAULT_WIN_TYPE (sizeof (shadeDefaultWinType) / sizeof (shadeDefaultWinType[0]))

static int displayPrivateIndex;

typedef struct _AnimDisplay
{
	int screenPrivateIndex;
	Atom wmHintsAtom;
	Atom winIconGeometryAtom;
	HandleEventProc handleEvent;
} AnimDisplay;

typedef enum
{
	// Event settings
	ANIM_SCREEN_OPTION_MINIMIZE_EFFECT = 0,
	ANIM_SCREEN_OPTION_MINIMIZE_WINDOW_TYPE,
	ANIM_SCREEN_OPTION_MINIMIZE_DURATION,
	ANIM_SCREEN_OPTION_MINIMIZE_RANDOM_EFFECTS,
	ANIM_SCREEN_OPTION_UNMINIMIZE_EFFECT,
	ANIM_SCREEN_OPTION_UNMINIMIZE_WINDOW_TYPE,
	ANIM_SCREEN_OPTION_UNMINIMIZE_DURATION,
	ANIM_SCREEN_OPTION_UNMINIMIZE_RANDOM_EFFECTS,
	ANIM_SCREEN_OPTION_CLOSE1_EFFECT,
	ANIM_SCREEN_OPTION_CLOSE1_WINDOW_TYPE,
	ANIM_SCREEN_OPTION_CLOSE1_DURATION,
	ANIM_SCREEN_OPTION_CLOSE1_RANDOM_EFFECTS,
	ANIM_SCREEN_OPTION_CREATE1_EFFECT,
	ANIM_SCREEN_OPTION_CREATE1_WINDOW_TYPE,
	ANIM_SCREEN_OPTION_CREATE1_DURATION,
	ANIM_SCREEN_OPTION_CREATE1_RANDOM_EFFECTS,
	ANIM_SCREEN_OPTION_CLOSE2_EFFECT,
	ANIM_SCREEN_OPTION_CLOSE2_WINDOW_TYPE,
	ANIM_SCREEN_OPTION_CLOSE2_DURATION,
	ANIM_SCREEN_OPTION_CLOSE2_RANDOM_EFFECTS,
	ANIM_SCREEN_OPTION_CREATE2_EFFECT,
	ANIM_SCREEN_OPTION_CREATE2_WINDOW_TYPE,
	ANIM_SCREEN_OPTION_CREATE2_DURATION,
	ANIM_SCREEN_OPTION_CREATE2_RANDOM_EFFECTS,
	ANIM_SCREEN_OPTION_FOCUS_EFFECT,
	ANIM_SCREEN_OPTION_FOCUS_WINDOW_TYPE,
	ANIM_SCREEN_OPTION_FOCUS_DURATION,
	ANIM_SCREEN_OPTION_SHADE_EFFECT,
	ANIM_SCREEN_OPTION_SHADE_WINDOW_TYPE,
	ANIM_SCREEN_OPTION_SHADE_DURATION,
	ANIM_SCREEN_OPTION_SHADE_RANDOM_EFFECTS,
	ANIM_SCREEN_OPTION_UNSHADE_EFFECT,
	ANIM_SCREEN_OPTION_UNSHADE_WINDOW_TYPE,
	ANIM_SCREEN_OPTION_UNSHADE_DURATION,
	ANIM_SCREEN_OPTION_UNSHADE_RANDOM_EFFECTS,
	ANIM_SCREEN_OPTION_ROLLUP_FIXED_INTERIOR,
	// Misc. settings
	ANIM_SCREEN_OPTION_ALL_RANDOM,
	ANIM_SCREEN_OPTION_ZOOM_FROM_CENTER,
	ANIM_SCREEN_OPTION_DISABLE_PP_FX,
	ANIM_SCREEN_OPTION_TIME_STEP,
	ANIM_SCREEN_OPTION_TIME_STEP_INTENSE,
	// Effect settings
	ANIM_SCREEN_OPTION_BEAMUP_SIZE,
	ANIM_SCREEN_OPTION_BEAMUP_SPACING,
	ANIM_SCREEN_OPTION_BEAMUP_COLOR,
	ANIM_SCREEN_OPTION_BEAMUP_SLOWDOWN,
	ANIM_SCREEN_OPTION_BEAMUP_LIFE,
	ANIM_SCREEN_OPTION_CURVED_FOLD_AMP,
	ANIM_SCREEN_OPTION_DOMINO_DIRECTION,
	ANIM_SCREEN_OPTION_RAZR_DIRECTION,
	ANIM_SCREEN_OPTION_FIRE_PARTICLES,
	ANIM_SCREEN_OPTION_FIRE_SIZE,
	ANIM_SCREEN_OPTION_FIRE_SLOWDOWN,
	ANIM_SCREEN_OPTION_FIRE_LIFE,
	ANIM_SCREEN_OPTION_FIRE_COLOR,
	ANIM_SCREEN_OPTION_FIRE_DIRECTION,
	ANIM_SCREEN_OPTION_FIRE_CONSTANT_SPEED,
	ANIM_SCREEN_OPTION_FIRE_SMOKE,
	ANIM_SCREEN_OPTION_FIRE_MYSTICAL,
	ANIM_SCREEN_OPTION_FOCUS_WAVE_WIDTH,
	ANIM_SCREEN_OPTION_FOCUS_WAVE_AMP,
	ANIM_SCREEN_OPTION_GLIDE1_AWAY_POS,
	ANIM_SCREEN_OPTION_GLIDE1_AWAY_ANGLE,
	ANIM_SCREEN_OPTION_GLIDE1_THICKNESS,
	ANIM_SCREEN_OPTION_GLIDE2_AWAY_POS,
	ANIM_SCREEN_OPTION_GLIDE2_AWAY_ANGLE,
	ANIM_SCREEN_OPTION_GLIDE2_THICKNESS,
	ANIM_SCREEN_OPTION_HORIZONTAL_FOLDS_AMP,
	ANIM_SCREEN_OPTION_HORIZONTAL_FOLDS_NUM_FOLDS,
	ANIM_SCREEN_OPTION_MAGIC_LAMP1_GRID_RES,
	ANIM_SCREEN_OPTION_MAGIC_LAMP1_MAX_WAVES,
	ANIM_SCREEN_OPTION_MAGIC_LAMP1_WAVE_AMP_MIN,
	ANIM_SCREEN_OPTION_MAGIC_LAMP1_WAVE_AMP_MAX,
	ANIM_SCREEN_OPTION_MAGIC_LAMP1_CREATE_START_WIDTH,
	ANIM_SCREEN_OPTION_MAGIC_LAMP2_GRID_RES,
	ANIM_SCREEN_OPTION_MAGIC_LAMP2_MAX_WAVES,
	ANIM_SCREEN_OPTION_MAGIC_LAMP2_WAVE_AMP_MIN,
	ANIM_SCREEN_OPTION_MAGIC_LAMP2_WAVE_AMP_MAX,
	ANIM_SCREEN_OPTION_MAGIC_LAMP2_CREATE_START_WIDTH,
	ANIM_SCREEN_OPTION_MAGIC_LAMP3_GRID_RES,
	ANIM_SCREEN_OPTION_MAGIC_LAMP3_MAX_WAVES,
	ANIM_SCREEN_OPTION_MAGIC_LAMP3_WAVE_AMP_MIN,
	ANIM_SCREEN_OPTION_MAGIC_LAMP3_WAVE_AMP_MAX,
	ANIM_SCREEN_OPTION_MAGIC_LAMP3_CREATE_START_WIDTH,
	ANIM_SCREEN_OPTION_SIDEKICK_NUM_ROTATIONS,

	ANIM_SCREEN_OPTION_NUM
} AnimScreenOptions;

typedef struct _AnimScreen
{
	int windowPrivateIndex;

	PreparePaintScreenProc preparePaintScreen;
	DonePaintScreenProc donePaintScreen;
	PaintScreenProc paintScreen;
	PaintWindowProc paintWindow;
	DamageWindowRectProc damageWindowRect;
	AddWindowGeometryProc addWindowGeometry;
	DrawWindowGeometryProc drawWindowGeometry;
	DrawWindowTextureProc drawWindowTexture;

	WindowResizeNotifyProc windowResizeNotify;
	WindowMoveNotifyProc windowMoveNotify;
	WindowGrabNotifyProc windowGrabNotify;
	WindowUngrabNotifyProc windowUngrabNotify;

	CompOption opt[ANIM_SCREEN_OPTION_NUM];

	PostprocessDisabling ppDisabling;

	ZoomFromCenter zoomFC;

	Bool animInProgress;
	AnimEffect minimizeEffect;
	AnimEffect unminimizeEffect;
	AnimEffect create1Effect;
	AnimEffect create2Effect;
	AnimEffect close1Effect;
	AnimEffect close2Effect;
	AnimEffect focusEffect;
	AnimEffect shadeEffect;
	AnimEffect unshadeEffect;
	unsigned int minimizeWMask;
	unsigned int unminimizeWMask;
	unsigned int create1WMask;
	unsigned int create2WMask;
	unsigned int close1WMask;
	unsigned int close2WMask;
	unsigned int focusWMask;
	unsigned int shadeWMask;
	unsigned int unshadeWMask;

	AnimEffect close1RandomEffects[NUM_CLOSE_EFFECT];
	AnimEffect close2RandomEffects[NUM_CLOSE_EFFECT];
	AnimEffect create1RandomEffects[NUM_CLOSE_EFFECT];
	AnimEffect create2RandomEffects[NUM_CLOSE_EFFECT];
	AnimEffect minimizeRandomEffects[NUM_MINIMIZE_EFFECT];
	AnimEffect unminimizeRandomEffects[NUM_MINIMIZE_EFFECT];
	AnimEffect shadeRandomEffects[NUM_SHADE_EFFECT];
	AnimEffect unshadeRandomEffects[NUM_SHADE_EFFECT];
	unsigned int nClose1RandomEffects;
	unsigned int nClose2RandomEffects;
	unsigned int nCreate1RandomEffects;
	unsigned int nCreate2RandomEffects;
	unsigned int nMinimizeRandomEffects;
	unsigned int nUnminimizeRandomEffects;
	unsigned int nShadeRandomEffects;
	unsigned int nUnshadeRandomEffects;
} AnimScreen;

typedef struct _AnimWindow
{
	Model *model;
	int numPs;
	ParticleSystem *ps;
	unsigned int state;
	unsigned int newState;

	PolygonSet *polygonSet;
	float mvm[16];

	Region drawRegion;
	Bool useDrawRegion;

	XRectangle icon;
	XRectangle origWindowRect;

	XRectangle lastKnownCoords;	// used to determine if paintWindow is drawing
								// on the viewport that the animation started

	float numZoomRotations;
	GLushort storedOpacity;
	float timestep;				// to be used in updateWindowAttribFunc

	int nDrawGeometryCalls;

	Bool animInitialized;		// whether the animation effect (not the window) is initialized
	float animTotalTime;
	float animRemainingTime;
	int animOverrideProgressDir;	// 0: default dir, 1: forward, 2: backward

	Bool nowShaded;
	Bool grabbed;

	WindowEvent curWindowEvent;
	AnimEffect curAnimEffect;

	int unmapCnt;
	int destroyCnt;

	int nClipsPassed;			// # of clips passed to animAddWindowGeometry so far
	// in this draw step
	Bool clipsUpdated;          // whether stored clips are updated in this anim step
	WindowPaintAttrib curPaintAttrib;
	CompTexture *curTexture;
	int curTextureFilter;
	int animatedAtom;

	int animFireDirection;
	int subEffectNo;			// For effects that share same functions
	Bool deceleratingMotion;	// For effects that have decel. motion
} AnimWindow;

typedef struct _AnimEffectProperties
{
	void (*updateWindowAttribFunc) (AnimScreen *, AnimWindow *,
									WindowPaintAttrib *);
	void (*prePaintWindowFunc) (CompScreen *, CompWindow *);
	void (*postPaintWindowFunc) (CompScreen *, CompWindow *);
	void (*animStepFunc) (CompScreen *, CompWindow *, float time);
	void (*initFunc) (CompScreen *, CompWindow *);
	void (*initGridFunc) (AnimScreen *, WindowEvent, int *, int *);
	void (*addCustomGeometryFunc) (CompScreen *, CompWindow *, int, Box *,
								   int, CompMatrix *);
	void (*drawCustomGeometryFunc) (CompScreen *, CompWindow *);
	Bool dontUseQTexCoord;		// TRUE if effect doesn't need Q coord.
	void (*animStepPolygonFunc) (CompWindow *, PolygonObject *, float);
	int subEffectNo;			// For effects that share same functions
} AnimEffectProperties;

AnimEffectProperties *animEffectPropertiesTmp;

#define GET_ANIM_DISPLAY(d)                                       \
    ((AnimDisplay *) (d)->privates[displayPrivateIndex].ptr)

#define ANIM_DISPLAY(d)                       \
    AnimDisplay *ad = GET_ANIM_DISPLAY (d)

#define GET_ANIM_SCREEN(s, ad)                                   \
    ((AnimScreen *) (s)->privates[(ad)->screenPrivateIndex].ptr)

#define ANIM_SCREEN(s)                                                      \
    AnimScreen *as = GET_ANIM_SCREEN (s, GET_ANIM_DISPLAY (s->display))

#define GET_ANIM_WINDOW(w, as)                                   \
    ((AnimWindow *) (w)->privates[(as)->windowPrivateIndex].ptr)

#define ANIM_WINDOW(w)                                         \
    AnimWindow *aw = GET_ANIM_WINDOW  (w,                         \
            GET_ANIM_SCREEN  (w->screen,                 \
                GET_ANIM_DISPLAY (w->screen->display)))

#define NUM_OPTIONS(s) (sizeof ((s)->opt) / sizeof (CompOption))

#define LIST_SIZE(l) (sizeof (l) / sizeof (l[0]))


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

#define STORE_RANDOM_EFFECT_LIST(option, nFxChoices, defaultValue, nStoredRandomFx, storedRandomFx, fxNames, fxIds) \
{ \
	CompOptionValue *effect = as->opt[(option)].value.list.value; \
	int nListItems = as->opt[(option)].value.list.nValue; \
	\
	as->nStoredRandomFx = 0; \
	int j; \
	for (j = 0; j < nListItems; j++, effect++) \
	{ \
		int i; \
		for (i = 2; i < (nFxChoices); i++) \
		{ \
			if (strcmp(effect->s, fxNames[i]) == 0) \
			{ \
				Bool duplicate = FALSE; \
				int k; \
				for (k = 0; k < as->nStoredRandomFx; k++) \
					if (as->storedRandomFx[k] == \
						fxIds[i]) \
					{ \
						duplicate = TRUE; \
						break; \
					} \
				if (!duplicate) \
				{ \
					as->storedRandomFx[as->nStoredRandomFx] \
						= fxIds[i]; \
					as->nStoredRandomFx++; \
				} \
				break; \
			} \
		} \
	} \
	if (nListItems == 0 || as->nStoredRandomFx == 0) \
	{ \
		as->storedRandomFx[0] = (defaultValue); \
		as->nStoredRandomFx = 1; \
	} \
}

#define INIT_RANDOM_EFFECT_LIST(poption, pname, pgroup, psubgroup, nFxChoices, fxNames) \
{ \
	o = &as->opt[(poption)]; \
	o->advanced = False; \
	o->name = pname "_random_effects"; \
	o->group = N_(pgroup); \
	o->subGroup = N_(psubgroup); \
	o->displayHints = ""; \
	o->shortDesc = N_("Random Effect Pool"); \
	o->longDesc = \
			N_("Pool of effects to be chosen from if Random effect is " \
			   "selected. Click reset to restore full list. If the list " \
			   "is empty, the default effect will be used."); \
	o->type = CompOptionTypeList; \
	o->value.list.type = CompOptionTypeString; \
	o->value.list.nValue = (nFxChoices) - 2; \
	o->value.list.value = \
		malloc(sizeof(CompOptionValue) * ((nFxChoices) - 2)); \
	for (i = 2; i < (nFxChoices); i++) \
		o->value.list.value[i - 2].s = strdup(fxNames[i]); \
	o->rest.s.string = fxNames+2; \
	o->rest.s.nString = (nFxChoices) - 2; \
}

#define sigmoid(fx) (1.0f/(1.0f+exp(-5.0f*2*((fx)-0.5))))
#define sigmoid2(fx, s) (1.0f/(1.0f+exp(-(s)*2*((fx)-0.5))))

static void modelCalcBounds(Model * model)
{
	int i;

	model->topLeft.x = MAXSHORT;
	model->topLeft.y = MAXSHORT;
	model->bottomRight.x = MINSHORT;
	model->bottomRight.y = MINSHORT;

	for (i = 0; i < model->numObjects; i++)
	{
		if (model->objects[i].position.x < model->topLeft.x)
			model->topLeft.x = model->objects[i].position.x;
		else if (model->objects[i].position.x > model->bottomRight.x)
			model->bottomRight.x = model->objects[i].position.x;

		if (model->objects[i].position.y < model->topLeft.y)
			model->topLeft.y = model->objects[i].position.y;
		else if (model->objects[i].position.y > model->bottomRight.y)
			model->bottomRight.y = model->objects[i].position.y;
	}
}

static Bool ensureLargerClipCapacity(PolygonSet * pset)
{
	if (pset->clipCapacity == pset->nClips)	// if list full
	{
		Clip4Polygons *newList = realloc
				(pset->clips, sizeof(Clip4Polygons) *
				 (pset->clipCapacity + ANIM_CLIP_LIST_INCREMENT));
		if (!newList)
			return FALSE;
		// reset newly allocated part of this memory to 0
		memset(newList + pset->clipCapacity,
			   0, sizeof(Clip4Polygons) * ANIM_CLIP_LIST_INCREMENT);

		int *newList2 = realloc
				(pset->lastClipInGroup, sizeof(int) *
				 (pset->clipCapacity + ANIM_CLIP_LIST_INCREMENT));
		if (!newList2)
		{
			free(newList);
			pset->clips = 0;
			pset->lastClipInGroup = 0;
			return FALSE;
		}
		// reset newly allocated part of this memory to 0
		memset(newList2 + pset->clipCapacity,
			   0, sizeof(int) * ANIM_CLIP_LIST_INCREMENT);

		pset->clips = newList;
		pset->clipCapacity += ANIM_CLIP_LIST_INCREMENT;
		pset->lastClipInGroup = newList2;
	}
	return TRUE;
}

static float defaultAnimProgress(AnimWindow * aw)
{
	float forwardProgress =
			1 - (aw->animRemainingTime - aw->timestep) /
			(aw->animTotalTime - aw->timestep);
	if (aw->curWindowEvent == WindowEventCreate ||
		aw->curWindowEvent == WindowEventUnminimize ||
		aw->curWindowEvent == WindowEventUnshade)
		forwardProgress = 1 - forwardProgress;

	return forwardProgress;
}

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

	AnimDirection dir = AnimDirectionUp;
	int i;

	for (i = 0; i < LIST_SIZE(animDirectionName); i++)
	{
		if (strcmp(optionValue, animDirectionName[i]) == 0)
		{
			dir = 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;
}

// 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
static 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))));
}

// default x limits for deceleration sigmoid
#define DECEL_SIG_DEF_START 0.9
#define DECEL_SIG_DEF_END 1.3

/*
static float decelerateProgress(float progress)
{
	return decelerateProgressCustom
		(progress, DECEL_SIG_DEF_START, DECEL_SIG_DEF_END);
}
*/

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


static void
applyTransformToObject(Object *obj, GLfloat *mat)
{
	Point3d o = obj->posRel3d;

	float x =
		o.x * mat[0] + o.y * mat[4] + o.z * mat[8] + 1 * mat[12];
	float y =
		o.x * mat[1] + o.y * mat[5] + o.z * mat[9] + 1 * mat[13];
	// ignore z
	float w =
		o.x * mat[3] + o.y * mat[7] + o.z * mat[11] + 1 * mat[15];

	obj->position.x = x / w;
	obj->position.y = y / w;
}

static void
obtainTransformMatrix (CompScreen *s, GLfloat *mat,
					   float rotAngle, Vector3d rotAxis,
					   Point3d translation)
{
	glPushMatrix();
	glLoadIdentity();

	// column-major order
	GLfloat perspMat[16] =
			{1, 0, 0, 0,
			 0, 1, 0, 0,
			 0, 0, 1, -1.0/s->width,
			 0, 0, 1, 1};

	glTranslatef(0, 0, DEFAULT_Z_CAMERA * s->width);

	glMultMatrixf(perspMat);

	glTranslatef(translation.x, translation.y, translation.z);

	glRotatef(rotAngle, rotAxis.x, rotAxis.y, rotAxis.z);

	glGetFloatv(GL_MODELVIEW_MATRIX, mat);
	glPopMatrix();
}


static void polygonsAnimStep(CompScreen * s, CompWindow * w, float time)
{
	int i, j, steps;

	ANIM_SCREEN(s);
	ANIM_WINDOW(w);

	Model *model = aw->model;

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

	aw->timestep = timestep;

	model->remainderSteps += time / timestep;
	steps = floor(model->remainderSteps);
	model->remainderSteps -= steps;
	if (!steps && aw->animRemainingTime < aw->animTotalTime)
		return;
	steps = MAX(1, steps);

	for (j = 0; j < steps; j++)
	{
		float forwardProgress = defaultAnimProgress(aw);

		if (aw->polygonSet)
		{
			if (animEffectPropertiesTmp[aw->curAnimEffect].
				animStepPolygonFunc)
			{
				for (i = 0; i < aw->polygonSet->nPolygons; i++)
					animEffectPropertiesTmp[aw->curAnimEffect].
							animStepPolygonFunc
							(w, &aw->polygonSet->polygons[i],
							 forwardProgress);
			}
		}
		else
			fprintf(stderr, "%s: pset null at line %d\n", __FILE__, __LINE__);
		aw->animRemainingTime -= timestep;
		if (aw->animRemainingTime <= 0)
		{
			aw->animRemainingTime = 0;	// avoid sub-zero values
			break;
		}
	}
	modelCalcBounds(model);
}


// =====================  Effect: Magic Lamp  =========================

static void
fxMagicLamp1InitGrid(AnimScreen * as,
					 WindowEvent forWindowEvent,
					 int *gridWidth, int *gridHeight)
{
	*gridWidth = 2;
	*gridHeight = as->opt[ANIM_SCREEN_OPTION_MAGIC_LAMP1_GRID_RES].value.i;
}

static void
fxMagicLamp2InitGrid(AnimScreen * as,
					 WindowEvent forWindowEvent,
					 int *gridWidth, int *gridHeight)
{
	*gridWidth = 2;
	*gridHeight = as->opt[ANIM_SCREEN_OPTION_MAGIC_LAMP2_GRID_RES].value.i;
}
static void
fxMagicLamp3InitGrid(AnimScreen * as, WindowEvent forWindowEvent,
					 int *gridWidth, int *gridHeight)
{
	*gridWidth = 2;
	*gridHeight = as->opt[ANIM_SCREEN_OPTION_MAGIC_LAMP3_GRID_RES].value.i;
}

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

	Model *model = aw->model;

	int screenHeight = s->height;
	Bool minimizeToTop = (WIN_Y(w) + WIN_H(w) / 2) >
			(aw->icon.y + aw->icon.height / 2);
	int maxWaves;
	float waveAmpMin, waveAmpMax;

	if (aw->curAnimEffect == AnimEffectMagicLamp1)
	{
		maxWaves = as->opt[ANIM_SCREEN_OPTION_MAGIC_LAMP1_MAX_WAVES].value.i;
		waveAmpMin =
				as->opt[ANIM_SCREEN_OPTION_MAGIC_LAMP1_WAVE_AMP_MIN].value.f;
		waveAmpMax =
				as->opt[ANIM_SCREEN_OPTION_MAGIC_LAMP1_WAVE_AMP_MAX].value.f;
	}
	else if (aw->curAnimEffect == AnimEffectMagicLamp2)
	{
		maxWaves = as->opt[ANIM_SCREEN_OPTION_MAGIC_LAMP2_MAX_WAVES].value.i;
		waveAmpMin =
				as->opt[ANIM_SCREEN_OPTION_MAGIC_LAMP2_WAVE_AMP_MIN].value.f;
		waveAmpMax =
				as->opt[ANIM_SCREEN_OPTION_MAGIC_LAMP2_WAVE_AMP_MAX].value.f;
	}
	else
	{
		maxWaves = as->opt[ANIM_SCREEN_OPTION_MAGIC_LAMP3_MAX_WAVES].value.i;
		waveAmpMin =
				as->opt[ANIM_SCREEN_OPTION_MAGIC_LAMP3_WAVE_AMP_MIN].value.f;
		waveAmpMax =
				as->opt[ANIM_SCREEN_OPTION_MAGIC_LAMP3_WAVE_AMP_MAX].value.f;
	}
	if (waveAmpMax < waveAmpMin)
		waveAmpMax = waveAmpMin;

	if (maxWaves > 0)
	{
		float distance;

		if (minimizeToTop)
			distance = WIN_Y(w) + WIN_H(w) - aw->icon.y;
		else
			distance = aw->icon.y - WIN_Y(w);

		// Initialize waves
		model->magicLampWaveCount =
				1 + (float)maxWaves *distance / screenHeight;

		if (!(model->magicLampWaves))
			model->magicLampWaves =
					calloc(1, model->magicLampWaveCount * sizeof(WaveParam));

		int ampDirection = (RAND_FLOAT() < 0.5 ? 1 : -1);
		int i;
		float minHalfWidth = 0.22f;
		float maxHalfWidth = 0.38f;

		for (i = 0; i < model->magicLampWaveCount; i++)
		{
			model->magicLampWaves[i].amp =
					ampDirection * (waveAmpMax - waveAmpMin) *
					rand() / RAND_MAX + ampDirection * waveAmpMin;
			model->magicLampWaves[i].halfWidth =
					RAND_FLOAT() * (maxHalfWidth -
									minHalfWidth) + minHalfWidth;

			// avoid offset at top and bottom part by added waves
			float availPos = 1 - 2 * model->magicLampWaves[i].halfWidth;
			float posInAvailSegment = 0;

			if (i > 0)
				posInAvailSegment =
						(availPos /
						 model->magicLampWaveCount) * rand() / RAND_MAX;

			model->magicLampWaves[i].pos =
					(posInAvailSegment +
					 i * availPos / model->magicLampWaveCount +
					 model->magicLampWaves[i].halfWidth);

			// switch wave direction
			ampDirection *= -1;
		}
	}
	else
		model->magicLampWaveCount = 0;
}

static void
fxMagicLampModelStepObject(CompWindow * w,
						   Model * model,
						   Object * object,
						   float forwardProgress, Bool minimizeToTop)
{
	ANIM_WINDOW(w);

	float iconCloseEndY;
	float iconFarEndY;
	float winFarEndY;
	float winVisibleCloseEndY;

	if (minimizeToTop)
	{
		iconFarEndY = aw->icon.y;
		iconCloseEndY = aw->icon.y + aw->icon.height;
		winFarEndY = WIN_Y(w) + WIN_H(w);
		winVisibleCloseEndY = WIN_Y(w);
		if (winVisibleCloseEndY < iconCloseEndY)
			winVisibleCloseEndY = iconCloseEndY;
	}
	else
	{
		iconFarEndY = aw->icon.y + aw->icon.height;
		iconCloseEndY = aw->icon.y;
		winFarEndY = WIN_Y(w);
		winVisibleCloseEndY = WIN_Y(w) + WIN_H(w);
		if (winVisibleCloseEndY > iconCloseEndY)
			winVisibleCloseEndY = iconCloseEndY;
	}

	float preShapePhaseEnd = 0.17f;
	float stretchPhaseEnd =
			preShapePhaseEnd + (1 - preShapePhaseEnd) *
			(iconCloseEndY -
			 winVisibleCloseEndY) / ((iconCloseEndY - winFarEndY) +
									 (iconCloseEndY - winVisibleCloseEndY));
	if (stretchPhaseEnd < preShapePhaseEnd + 0.1)
		stretchPhaseEnd = preShapePhaseEnd + 0.1;

	float origx = w->attrib.x + (WIN_W(w) * object->gridPosition.x -
								 w->output.left) * model->scale.x;
	float origy = w->attrib.y + (WIN_H(w) * object->gridPosition.y -
								 w->output.top) * model->scale.y;

	float iconShadowLeft =
			((float)(w->output.left - w->input.left)) * aw->icon.width /
			w->width;
	float iconShadowRight =
			((float)(w->output.right - w->input.right)) * aw->icon.width /
			w->width;
	float iconx =
			(aw->icon.x - iconShadowLeft) + (aw->icon.width + iconShadowLeft +
											 iconShadowRight) *
			object->gridPosition.x;
	float icony = aw->icon.y + aw->icon.height * object->gridPosition.y;

	if (forwardProgress < preShapePhaseEnd)
	{
		float preShapeProgress = forwardProgress / preShapePhaseEnd;
		float fx =
				(iconCloseEndY - object->position.y) / (iconCloseEndY -
														winFarEndY);
		float fy = (sigmoid(fx) - sigmoid(0)) / (sigmoid(1) - sigmoid(0));
		int i;
		float targetx = fy * (origx - iconx) + iconx;

		for (i = 0; i < model->magicLampWaveCount; i++)
		{
			float cosfx =
					(fx -
					 model->magicLampWaves[i].pos) /
					model->magicLampWaves[i].halfWidth;
			if (cosfx < -1 || cosfx > 1)
				continue;
			targetx +=
					model->magicLampWaves[i].amp * model->scale.x *
					(cos(cosfx * M_PI) + 1) / 2;
		}
		object->position.x =
				(1 - preShapeProgress) * origx + preShapeProgress * targetx;
		object->position.y = origy;
	}
	else
	{
		float stretchedPos;

		if (minimizeToTop)
			stretchedPos =
					object->gridPosition.y * origy +
					(1 - object->gridPosition.y) * icony;
		else
			stretchedPos =
					(1 - object->gridPosition.y) * origy +
					object->gridPosition.y * icony;

		if (forwardProgress < stretchPhaseEnd)
		{
			float stretchProgress =
					(forwardProgress - preShapePhaseEnd) /
					(stretchPhaseEnd - preShapePhaseEnd);

			object->position.y =
					(1 - stretchProgress) * origy +
					stretchProgress * stretchedPos;
		}
		else
		{
			float postStretchProgress =
					(forwardProgress - stretchPhaseEnd) / (1 -
														   stretchPhaseEnd);

			object->position.y =
					(1 - postStretchProgress) *
					stretchedPos +
					postStretchProgress *
					(stretchedPos + (iconCloseEndY - winFarEndY));
		}
		float fx =
				(iconCloseEndY - object->position.y) / (iconCloseEndY -
														winFarEndY);
		float fy = (sigmoid(fx) - sigmoid(0)) / (sigmoid(1) - sigmoid(0));

		int i;

		object->position.x = fy * (origx - iconx) + iconx;
		for (i = 0; i < model->magicLampWaveCount; i++)
		{
			float cosfx =
					(fx -
					 model->magicLampWaves[i].pos) /
					model->magicLampWaves[i].halfWidth;
			if (cosfx < -1 || cosfx > 1)
				continue;
			object->position.x +=
					model->magicLampWaves[i].amp * model->scale.x *
					(cos(cosfx * M_PI) + 1) / 2;
		}
	}
	if (minimizeToTop)
	{
		if (object->position.y < iconFarEndY)
			object->position.y = iconFarEndY;
	}
	else
	{
		if (object->position.y > iconFarEndY)
			object->position.y = iconFarEndY;
	}
}

static void fxMagicLampModelStep(CompScreen * s, CompWindow * w, float time)
{
	int i, j, steps;

	ANIM_SCREEN(s);
	ANIM_WINDOW(w);

	Model *model = aw->model;

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

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

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

	Bool minimizeToTop = (WIN_Y(w) + WIN_H(w) / 2) >
			(aw->icon.y + aw->icon.height / 2);

	for (j = 0; j < steps; j++)
	{
		float forwardProgress =
				1 - (aw->animRemainingTime - timestep) /
				(aw->animTotalTime - timestep);
		if (aw->curWindowEvent == WindowEventUnminimize ||
			aw->curWindowEvent == WindowEventCreate)
			forwardProgress = 1 - forwardProgress;

		for (i = 0; i < model->numObjects; i++)
		{
			fxMagicLampModelStepObject(w, model,
									   &model->objects[i],
									   forwardProgress, minimizeToTop);
		}
		aw->animRemainingTime -= timestep;
		if (aw->animRemainingTime <= 0)
		{
			aw->animRemainingTime = 0;	// avoid sub-zero values
			break;
		}
	}
	modelCalcBounds(model);
}


// =====================  Effect: Dream  =========================

static void fxDreamInit(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);
}

static void
fxDreamModelStepObject(CompWindow * w,
					   Model * model, Object * object, float forwardProgress)
{
	float waveAmpMax = MIN(WIN_H(w), WIN_W(w)) * 0.125f;
	float waveWidth = 10.0f;
	float waveSpeed = 7.0f;

	float origx = w->attrib.x + (WIN_W(w) * object->gridPosition.x -
								 w->output.left) * model->scale.x;
	float origy = w->attrib.y + (WIN_H(w) * object->gridPosition.y -
								 w->output.top) * model->scale.y;

	object->position.y = origy;
	object->position.x =
			origx +
			forwardProgress * waveAmpMax * model->scale.x *
			sin(object->gridPosition.y * M_PI * waveWidth +
				waveSpeed * forwardProgress);

}

static void fxDreamModelStep(CompScreen * s, CompWindow * w, float time)
{
	int i, j, steps;

	ANIM_WINDOW(w);
	ANIM_SCREEN(s);

	Model *model = aw->model;

	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;

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

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

	for (j = 0; j < steps; j++)
	{
		float forwardProgress =
				1 - (aw->animRemainingTime - timestep) /
				(aw->animTotalTime - timestep);
		if (aw->curWindowEvent == WindowEventCreate ||
			aw->curWindowEvent == WindowEventUnminimize)
			forwardProgress = 1 - forwardProgress;

		for (i = 0; i < model->numObjects; i++)
		{
			fxDreamModelStepObject(w,
								   model,
								   &model->objects[i], forwardProgress);
		}
		aw->animRemainingTime -= timestep;
		if (aw->animRemainingTime <= 0)
		{
			aw->animRemainingTime = 0;	// avoid sub-zero values
			break;
		}
	}
	modelCalcBounds(model);
}

static void
fxDreamUpdateWindowAttrib(AnimScreen * as,
						  AnimWindow * aw, WindowPaintAttrib * wAttrib)
{
	float forwardProgress =
			1 - (aw->animRemainingTime - aw->timestep) /
			(aw->animTotalTime - aw->timestep);
	forwardProgress = MIN(forwardProgress, 1);
	forwardProgress = MAX(forwardProgress, 0);

	if (aw->curWindowEvent == WindowEventCreate ||
		aw->curWindowEvent == WindowEventUnminimize)
		forwardProgress = 1 - forwardProgress;

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


// =====================  Effect: Wave  =========================

static void
fxWaveModelStepObject(CompWindow * w,
					  Model * model,
					  Object * object,
					  float forwardProgress,
					  float waveAmp, float waveHalfWidth)
{
	float origx = w->attrib.x + (WIN_W(w) * object->gridPosition.x -
								 w->output.left) * model->scale.x;
	float origy = w->attrib.y + (WIN_H(w) * object->gridPosition.y -
								 w->output.top) * model->scale.y;

	float wavePosition =
			WIN_Y(w) - waveHalfWidth +
			forwardProgress * (WIN_H(w) * model->scale.y + 2 * waveHalfWidth);

	object->position.y = origy;
	object->position.x = origx;

	if (fabs(object->position.y - wavePosition) < waveHalfWidth)
		object->position.x +=
				(object->gridPosition.x - 0.5) * waveAmp *
				(cos
				 ((object->position.y -
				   wavePosition) * M_PI / waveHalfWidth) + 1) / 2;
}

static void fxWaveModelStep(CompScreen * s, CompWindow * w, float time)
{
	int i, j, steps;

	ANIM_SCREEN(s);
	ANIM_WINDOW(w);

	Model *model = aw->model;

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

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

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

	for (j = 0; j < steps; j++)
	{
		float forwardProgress =
				1 - (aw->animRemainingTime - timestep) /
				(aw->animTotalTime - timestep);

		for (i = 0; i < model->numObjects; i++)
		{
			fxWaveModelStepObject(w,
								  model,
								  &model->objects[i],
								  forwardProgress,
								  WIN_H(w) * model->scale.y *
								  as->
								  opt
								  [ANIM_SCREEN_OPTION_FOCUS_WAVE_AMP].
								  value.f,
								  WIN_H(w) * model->scale.y *
								  as->
								  opt
								  [ANIM_SCREEN_OPTION_FOCUS_WAVE_WIDTH].
								  value.f / 2);
		}
		aw->animRemainingTime -= timestep;
		if (aw->animRemainingTime <= 0)
		{
			aw->animRemainingTime = 0;	// avoid sub-zero values
			break;
		}
	}
	modelCalcBounds(model);
}


// =====================  Effect: Zoom and Sidekick  =========================

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

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

	// 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);
}

static void fxZoomInit(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);
}

// Provides the animation progress for Zoom
static float fxZoomAnimProgressDirCustom(AnimWindow * aw,
										 float maxSigX)
{
	// maxSig is the max x for the deceleration sigmoid

	float forwardProgress =
			1 - (aw->animRemainingTime - aw->timestep) /
			(aw->animTotalTime - aw->timestep);

	int animProgressDir = 1;

	if (aw->curWindowEvent == WindowEventUnminimize ||
		aw->curWindowEvent == WindowEventCreate)
		animProgressDir = 2;

	if (aw->animOverrideProgressDir != 0)
		animProgressDir = aw->animOverrideProgressDir;

	float x = forwardProgress;

	if ((animProgressDir == 1 &&
		 (aw->curWindowEvent == WindowEventUnminimize ||
		  aw->curWindowEvent == WindowEventCreate)) ||
		(animProgressDir == 2 &&
		 (aw->curWindowEvent == WindowEventMinimize ||
		  aw->curWindowEvent == WindowEventClose)))
		x = 1 - forwardProgress;

	forwardProgress = decelerateProgressCustom
		(1 - x, DECEL_SIG_DEF_START, maxSigX);

	if (animProgressDir == 1)
		forwardProgress = 1 - forwardProgress;

	return forwardProgress;
}

static float fxZoomAnimProgressDir(AnimWindow * aw)
{
	return fxZoomAnimProgressDirCustom(aw, DECEL_SIG_DEF_END);
}

static void
fxSidekickModelStepObject(CompWindow * w,
						  Model * model,
						  Object * object,
						  Point currentCenter, Point currentSize,
						  float sinRot, float cosRot)
{
	float x =
		currentCenter.x - currentSize.x / 2 +
		currentSize.x * object->gridPosition.x;
	float y =
		currentCenter.y - currentSize.y / 2 +
		currentSize.y * object->gridPosition.y;

	x -= currentCenter.x;
	y -= currentCenter.y;

	object->position.x = cosRot * x - sinRot * y;
	object->position.y = sinRot * x + cosRot * y;

	object->position.x += currentCenter.x;
	object->position.y += currentCenter.y;
}

static void
fxZoomModelStepObject(CompScreen *s, CompWindow * w,
					  Model * model, Object * object,
					  Point currentCenter, Point currentSize)
{
	object->position.x =
		currentCenter.x - currentSize.x / 2 +
		currentSize.x * object->gridPosition.x;
	object->position.y =
		currentCenter.y - currentSize.y / 2 +
		currentSize.y * object->gridPosition.y;
}

static void fxZoomModelStep(CompScreen * s, CompWindow * w, float time)
{
	int i, j, steps;

	ANIM_SCREEN(s);
	ANIM_WINDOW(w);

	Model *model = aw->model;

	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;

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

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

	for (j = 0; j < steps; j++)
	{
		float sinRot = 0;
		float cosRot = 0;

		Point winCenter =
			{(WIN_X(w) + WIN_W(w) * model->scale.x / 2),
			 (WIN_Y(w) + WIN_H(w) * model->scale.y / 2)};
		Point iconCenter =
			{aw->icon.x + aw->icon.width / 2,
			 aw->icon.y + aw->icon.height / 2};

		Point winSize =
			{WIN_W(w) * model->scale.x,
			 WIN_H(w) * model->scale.y};

		//float xDiff = fabs(winCenter.x - iconCenter.x);
		//float yDiff = fabs(winCenter.y - iconCenter.y);
		float xMaxSig;
		float yMaxSig;

		xMaxSig = DECEL_SIG_DEF_END;
		yMaxSig = DECEL_SIG_DEF_END;

		float xProgress = fxZoomAnimProgressDirCustom(aw, xMaxSig);
		float yProgress = fxZoomAnimProgressDirCustom(aw, yMaxSig);
		float progress = (xProgress + yProgress) / 2;

		Point currentCenter =
			{(1 - xProgress) * winCenter.x + xProgress * iconCenter.x,
			 (1 - yProgress) * winCenter.y + yProgress * iconCenter.y};
		Point currentSize =
			{(1 - progress) * winSize.x + progress * aw->icon.width,
			 (1 - progress) * winSize.y + progress * aw->icon.height};

		if (aw->curAnimEffect == AnimEffectSidekick)
		{
			sinRot = sin(2 * M_PI * progress * aw->numZoomRotations);
			cosRot = cos(2 * M_PI * progress * aw->numZoomRotations);

			for (i = 0; i < model->numObjects; i++)
				fxSidekickModelStepObject(w, model,
										  &model->
										  objects[i],
										  currentCenter, currentSize,
										  sinRot, cosRot);
		}
		else
			for (i = 0; i < model->numObjects; i++)
				fxZoomModelStepObject(s, w, model,
									  &model->objects[i],
									  currentCenter, currentSize);

		aw->animRemainingTime -= timestep;
		if (aw->animRemainingTime <= 0)
		{
			aw->animRemainingTime = 0;	// avoid sub-zero values
			break;
		}
	}
	modelCalcBounds(model);
}

static void
fxZoomUpdateWindowAttrib(AnimScreen * as,
						 AnimWindow * aw, WindowPaintAttrib * wAttrib)
{
	if (aw->model->scale.x < 1.0 &&	// if Scale plugin in progress
		aw->curWindowEvent == WindowEventUnminimize)	// and if unminimizing
		return;					// then allow Fade to take over opacity
	float forwardProgress = fxZoomAnimProgressDir(aw);

	forwardProgress = MIN(forwardProgress, 1);
	forwardProgress = MAX(forwardProgress, 0);

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


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

static void fxGlideGetParams
	(AnimScreen *as, AnimWindow *aw,
	 float *finalDistFac, float *finalRotAng, float *thickness)
{
	// Sub effects:
	// 1: Glide 1
	// 2: Glide 2
	if (aw->subEffectNo == 1)
	{
		*finalDistFac = as->opt[ANIM_SCREEN_OPTION_GLIDE1_AWAY_POS].value.f;
		*finalRotAng = as->opt[ANIM_SCREEN_OPTION_GLIDE1_AWAY_ANGLE].value.f;
		*thickness = as->opt[ANIM_SCREEN_OPTION_GLIDE1_THICKNESS].value.f;
	}
	else
	{
		*finalDistFac = as->opt[ANIM_SCREEN_OPTION_GLIDE2_AWAY_POS].value.f;
		*finalRotAng = as->opt[ANIM_SCREEN_OPTION_GLIDE2_AWAY_ANGLE].value.f;
		*thickness = as->opt[ANIM_SCREEN_OPTION_GLIDE2_THICKNESS].value.f;
	}

}

static float fxGlideAnimProgress(AnimWindow * aw)
{
	float forwardProgress =
			1 - (aw->animRemainingTime - aw->timestep) /
			(aw->animTotalTime - aw->timestep);
	if (aw->curWindowEvent == WindowEventCreate ||
		aw->curWindowEvent == WindowEventUnminimize ||
		aw->curWindowEvent == WindowEventUnshade)
		forwardProgress = 1 - forwardProgress;

	return decelerateProgress2(forwardProgress);
}

static void
fxGlideModelStepObject(CompWindow * w,
					   Model * model,
					   Object * obj,
					   GLfloat *mat,
					   Point3d rotAxisOffset)
{
	float origx = w->attrib.x + (WIN_W(w) * obj->gridPosition.x -
								 w->output.left) * model->scale.x;
	float origy = w->attrib.y + (WIN_H(w) * obj->gridPosition.y -
								 w->output.top) * model->scale.y;

	obj->posRel3d.x = origx - rotAxisOffset.x;
	obj->posRel3d.y = origy - rotAxisOffset.y;

	applyTransformToObject(obj, mat);
	obj->position.x += rotAxisOffset.x;
	obj->position.y += rotAxisOffset.y;
}

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

	float finalDistFac;
	float finalRotAng;
	float thickness;

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

	if (thickness > 1e-5) // the effect is 3D
	{
		polygonsAnimStep(s, w, time); // do 3D anim step instead
		return;
	}

	// for 2D glide effect
	// ------------------------

	int i, j, steps;
	Model *model = aw->model;

	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;

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

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

	for (j = 0; j < steps; j++)
	{
		float forwardProgress = fxGlideAnimProgress(aw);
		float finalz = finalDistFac * 0.8 * DEFAULT_Z_CAMERA * s->width;

		Vector3d rotAxis = {1, 0, 0};
		Point3d rotAxisOffset =
			{WIN_X(w) + WIN_W(w) * model->scale.x / 2,
			 WIN_Y(w) + WIN_H(w) * model->scale.y / 2,
			 0};
		Point3d modelPos = {0, 0, finalz * forwardProgress};

		GLfloat mat[16];
		obtainTransformMatrix(s, mat, finalRotAng * forwardProgress,
							  rotAxis, modelPos);
		for (i = 0; i < model->numObjects; i++)
			fxGlideModelStepObject(w, model,
								   &model->objects[i],
								   mat,
								   rotAxisOffset);

		aw->animRemainingTime -= timestep;
		if (aw->animRemainingTime <= 0)
		{
			aw->animRemainingTime = 0;	// avoid sub-zero values
			break;
		}
	}
	modelCalcBounds(model);
}

static void
fxGlideUpdateWindowAttrib(AnimScreen * as,
						 AnimWindow * aw, WindowPaintAttrib * wAttrib)
{
	float finalDistFac;
	float finalRotAng;
	float thickness;

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

	if (thickness > 1e-5) // the effect is 3D
		return;

	// the effect is 2D

	if (aw->model->scale.x < 1.0 &&	// if Scale plugin in progress
		aw->curWindowEvent == WindowEventUnminimize)	// and if unminimizing
		return;					// then allow Fade to take over opacity
	float forwardProgress = fxGlideAnimProgress(aw);

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


// =====================  Effect: Curved Fold  =========================

static void
fxCurvedFoldModelStepObject(CompWindow * w,
							Model * model,
							Object * object,
							float forwardProgress, float curveMaxAmp)
{
	ANIM_WINDOW(w);

	float origx = w->attrib.x + (WIN_W(w) * object->gridPosition.x -
								 w->output.left) * model->scale.x;
	float origy = w->attrib.y + (WIN_H(w) * object->gridPosition.y -
								 w->output.top) * model->scale.y;

	if (aw->curWindowEvent == WindowEventShade ||
		aw->curWindowEvent == WindowEventUnshade)
	{
		// Execute shade mode

		// find position in window contents
		// (window contents correspond to 0.0-1.0 range)
		float relPosInWinContents =
				(object->gridPosition.y * WIN_H(w) -
				 model->topHeight) / w->height;
		float relDistToCenter = fabs(relPosInWinContents - 0.5);

		if (object->gridPosition.y == 0)
		{
			object->position.x = origx;
			object->position.y = WIN_Y(w);
		}
		else if (object->gridPosition.y == 1)
		{
			object->position.x = origx;
			object->position.y =
					(1 - forwardProgress) *
					origy +
					forwardProgress *
					(WIN_Y(w) + model->topHeight + model->bottomHeight);
		}
		else
		{
			object->position.x =
					origx + sin(forwardProgress * M_PI / 2) *
					(0.5 -
					 object->gridPosition.x) * 2 * model->scale.x *
					(curveMaxAmp -
					 curveMaxAmp * 4 * relDistToCenter * relDistToCenter);
			object->position.y =
					(1 - forwardProgress) * origy +
					forwardProgress * (WIN_Y(w) + model->topHeight);
		}
	}
	else
	{							// Execute normal mode

		// find position within window borders
		// (border contents correspond to 0.0-1.0 range)
		float relPosInWinBorders =
				(object->gridPosition.y * WIN_H(w) -
				 (w->output.top - w->input.top)) / BORDER_H(w);
		float relDistToCenter = fabs(relPosInWinBorders - 0.5);

		// prevent top & bottom shadows from extending too much
		if (relDistToCenter > 0.5)
			relDistToCenter = 0.5;

		object->position.x =
				origx + sin(forwardProgress * M_PI / 2) *
				(0.5 - object->gridPosition.x) * 2 * model->scale.x *
				(curveMaxAmp -
				 curveMaxAmp * 4 * relDistToCenter * relDistToCenter);
		object->position.y =
				(1 - forwardProgress) * origy + forwardProgress * BORDER_Y(w);
	}
}

static void fxCurvedFoldModelStep(CompScreen * s, CompWindow * w, float time)
{
	int i, j, steps;

	ANIM_SCREEN(s);
	ANIM_WINDOW(w);

	Model *model = aw->model;

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

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

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

	for (j = 0; j < steps; j++)
	{
		float forwardProgress =
				1 - (aw->animRemainingTime - timestep) /
				(aw->animTotalTime - timestep);
		if (aw->curWindowEvent == WindowEventCreate ||
			aw->curWindowEvent == WindowEventUnminimize ||
			aw->curWindowEvent == WindowEventUnshade)
			forwardProgress = 1 - forwardProgress;

		for (i = 0; i < model->numObjects; i++)
			fxCurvedFoldModelStepObject(w, model,
										&model->objects[i],
										forwardProgress,
										as->
										opt
										[ANIM_SCREEN_OPTION_CURVED_FOLD_AMP].
										value.f * WIN_W(w));

		aw->animRemainingTime -= timestep;
		if (aw->animRemainingTime <= 0)
		{
			aw->animRemainingTime = 0;	// avoid sub-zero values
			break;
		}
	}
	modelCalcBounds(model);
}


// =====================  Effect: Horizontal Folds  =========================


static void
fxHorizontalFoldsInitGrid(AnimScreen * as,
						  WindowEvent forWindowEvent,
						  int *gridWidth, int *gridHeight)
{
	*gridWidth = 2;
	if (forWindowEvent == WindowEventShade ||
		forWindowEvent == WindowEventUnshade)
		*gridHeight =
				3 + 2 *
				as->opt[ANIM_SCREEN_OPTION_HORIZONTAL_FOLDS_NUM_FOLDS].value.
				i;
	else
		*gridHeight =
				1 + 2 *
				as->opt[ANIM_SCREEN_OPTION_HORIZONTAL_FOLDS_NUM_FOLDS].value.
				i;
}

static void
fxHorizontalFoldsModelStepObject(CompWindow * w,
								 Model * model,
								 Object * object,
								 float forwardProgress,
								 float curveMaxAmp, int rowNo)
{
	ANIM_WINDOW(w);

	float origx = w->attrib.x + (WIN_W(w) * object->gridPosition.x -
								 w->output.left) * model->scale.x;
	float origy = w->attrib.y + (WIN_H(w) * object->gridPosition.y -
								 w->output.top) * model->scale.y;

	if (aw->curWindowEvent == WindowEventShade ||
		aw->curWindowEvent == WindowEventUnshade)
	{
		// Execute shade mode

		float relDistToFoldCenter = (rowNo % 2 == 1 ? 0.5 : 0);

		if (object->gridPosition.y == 0)
		{
			object->position.x = origx;
			object->position.y = WIN_Y(w);
		}
		else if (object->gridPosition.y == 1)
		{
			object->position.x = origx;
			object->position.y =
					(1 - forwardProgress) * origy +
					forwardProgress *
					(WIN_Y(w) + model->topHeight + model->bottomHeight);
		}
		else
		{
			object->position.x =
					origx + sin(forwardProgress * M_PI / 2) *
					(0.5 -
					 object->gridPosition.x) * 2 * model->scale.x *
					(curveMaxAmp -
					 curveMaxAmp * 4 * relDistToFoldCenter *
					 relDistToFoldCenter);
			object->position.y =
					(1 - forwardProgress) * origy +
					forwardProgress * (WIN_Y(w) + model->topHeight);
		}
	}
	else
	{							// Execute normal mode

		float relDistToFoldCenter;

		relDistToFoldCenter = (rowNo % 2 == 0 ? 0.5 : 0);

		object->position.x =
				origx + sin(forwardProgress * M_PI / 2) *
				(0.5 - object->gridPosition.x) * 2 * model->scale.x *
				(curveMaxAmp - curveMaxAmp * 4 *
				 relDistToFoldCenter * relDistToFoldCenter);
		object->position.y =
				(1 - forwardProgress) * origy + forwardProgress * BORDER_Y(w);
	}
}

static void
fxHorizontalFoldsModelStep(CompScreen * s, CompWindow * w, float time)
{
	int i, j, steps;

	ANIM_SCREEN(s);
	ANIM_WINDOW(w);

	Model *model = aw->model;

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

	model->remainderSteps += time / timestep;
	steps = floor(model->remainderSteps);
	model->remainderSteps -= steps;
	if (!steps && aw->animRemainingTime < aw->animTotalTime)
		return;
	steps = MAX(1, steps);

	for (j = 0; j < steps; j++)
	{
		float forwardProgress =
				1 - (aw->animRemainingTime - timestep) /
				(aw->animTotalTime - timestep);
		if (aw->curWindowEvent == WindowEventCreate ||
			aw->curWindowEvent == WindowEventUnminimize ||
			aw->curWindowEvent == WindowEventUnshade)
			forwardProgress = 1 - forwardProgress;

		for (i = 0; i < model->numObjects; i++)
			fxHorizontalFoldsModelStepObject(w, model,
											 &model->
											 objects[i],
											 forwardProgress,
											 as->
											 opt
											 [ANIM_SCREEN_OPTION_HORIZONTAL_FOLDS_AMP].
											 value.f *
											 WIN_W(w), i / model->gridWidth);

		aw->animRemainingTime -= timestep;
		if (aw->animRemainingTime <= 0)
		{
			aw->animRemainingTime = 0;	// avoid sub-zero values
			break;
		}
	}
	modelCalcBounds(model);
}


// =====================  Effect: Roll Up  =========================

static void
fxRollUpInitGrid(AnimScreen * as,
				 WindowEvent forWindowEvent, int *gridWidth, int *gridHeight)
{
	*gridWidth = 2;
	if (forWindowEvent == WindowEventShade ||
		forWindowEvent == WindowEventUnshade)
		*gridHeight = 4;
	else
		*gridHeight = 2;
}

static void
fxRollUpModelStepObject(CompWindow * w,
						Model * model,
						Object * object,
						float forwardProgress, Bool fixedInterior)
{
	ANIM_WINDOW(w);

	float origx = WIN_X(w) + WIN_W(w) * object->gridPosition.x;

	if (aw->curWindowEvent == WindowEventShade ||
		aw->curWindowEvent == WindowEventUnshade)
	{
		// Execute shade mode

		// find position in window contents
		// (window contents correspond to 0.0-1.0 range)
		float relPosInWinContents =
				(object->gridPosition.y * WIN_H(w) -
				 model->topHeight) / w->height;

		if (object->gridPosition.y == 0)
		{
			object->position.x = origx;
			object->position.y = WIN_Y(w);
		}
		else if (object->gridPosition.y == 1)
		{
			object->position.x = origx;
			object->position.y =
					(1 - forwardProgress) *
					(WIN_Y(w) +
					 WIN_H(w) * object->gridPosition.y) +
					forwardProgress * (WIN_Y(w) +
									   model->topHeight +
									   model->bottomHeight);
		}
		else
		{
			object->position.x = origx;

			if (relPosInWinContents > forwardProgress)
			{
				object->position.y =
						(1 - forwardProgress) *
						(WIN_Y(w) +
						 WIN_H(w) * object->gridPosition.y) +
						forwardProgress * (WIN_Y(w) + model->topHeight);

				if (fixedInterior)
					object->offsetTexCoordForQuadBefore.y =
							-forwardProgress * w->height;
			}
			else
			{
				object->position.y = WIN_Y(w) + model->topHeight;
				if (!fixedInterior)
					object->offsetTexCoordForQuadAfter.
							y =
							(forwardProgress -
							 relPosInWinContents) * w->height;
			}
		}
	}
}

static void fxRollUpModelStep(CompScreen * s, CompWindow * w, float time)
{
	int i, j, steps;

	ANIM_SCREEN(s);
	ANIM_WINDOW(w);

	Model *model = aw->model;

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

	model->remainderSteps += time / timestep;
	steps = floor(model->remainderSteps);
	model->remainderSteps -= steps;
	if (!steps && aw->animRemainingTime < aw->animTotalTime)
		return;
	steps = MAX(1, steps);

	for (j = 0; j < steps; j++)
	{
		float forwardProgress =
				1 - (aw->animRemainingTime - timestep) /
				(aw->animTotalTime - timestep);

		forwardProgress =
			(sigmoid(forwardProgress) - sigmoid(0)) /
			(sigmoid(1) - sigmoid(0));

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

		for (i = 0; i < model->numObjects; i++)
			fxRollUpModelStepObject(w, model,
									&model->objects[i],
									forwardProgress,
									as->
									opt
									[ANIM_SCREEN_OPTION_ROLLUP_FIXED_INTERIOR].
									value.b);

		aw->animRemainingTime -= timestep;
		if (aw->animRemainingTime <= 0)
		{
			aw->animRemainingTime = 0;	// avoid sub-zero values
			break;
		}
	}
	modelCalcBounds(model);
}


// =====================  Effect: Fade  =========================

static void fxFadeInit(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);
}

static void fxFadeModelStep(CompScreen * s, CompWindow * w, float time)
{
	int j, steps;

	ANIM_WINDOW(w);
	ANIM_SCREEN(s);

	Model *model = aw->model;

	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;

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

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

	for (j = 0; j < steps; j++)
	{
		aw->animRemainingTime -= timestep;
		if (aw->animRemainingTime <= 0)
		{
			aw->animRemainingTime = 0;	// avoid sub-zero values
			break;
		}
	}
}

static void
fxFadeUpdateWindowAttrib(AnimScreen * as,
						 AnimWindow * aw, WindowPaintAttrib * wAttrib)
{
	float forwardProgress =
			1 - (aw->animRemainingTime - aw->timestep) /
			(aw->animTotalTime - aw->timestep);
	forwardProgress = MIN(forwardProgress, 1);
	forwardProgress = MAX(forwardProgress, 0);

	if (aw->curWindowEvent == WindowEventCreate ||
		aw->curWindowEvent == WindowEventUnminimize)
		forwardProgress = 1 - forwardProgress;

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


// =====================  Effect: Burn  =========================

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

	modelInitObjects(aw->model, WIN_X(w), WIN_Y(w), WIN_W(w), WIN_H(w));
	if (!aw->numPs)
	{
		aw->ps = calloc(1, 2 * sizeof(ParticleSystem));
		if (!aw->ps)
		{
			postAnimationCleanup(w, TRUE);
			return;
		}
		aw->numPs = 2;
	}
	initParticles(as->opt[ANIM_SCREEN_OPTION_FIRE_PARTICLES].value.i /
				  10, &aw->ps[0]);
	initParticles(as->opt[ANIM_SCREEN_OPTION_FIRE_PARTICLES].value.i,
				  &aw->ps[1]);
	aw->ps[1].slowdown = as->opt[ANIM_SCREEN_OPTION_FIRE_SLOWDOWN].value.f;
	aw->ps[1].darken = 0.5;
	aw->ps[1].blendMode = GL_ONE;

	aw->ps[0].slowdown =
			as->opt[ANIM_SCREEN_OPTION_FIRE_SLOWDOWN].value.f / 2.0;
	aw->ps[0].darken = 0.0;
	aw->ps[0].blendMode = GL_ONE_MINUS_SRC_ALPHA;

	if (!aw->ps[0].tex)
		glGenTextures(1, &aw->ps[0].tex);
	glBindTexture(GL_TEXTURE_2D, aw->ps[0].tex);

	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 32, 32, 0,
				 GL_RGBA, GL_UNSIGNED_BYTE, fireTex);
	glBindTexture(GL_TEXTURE_2D, 0);

	if (!aw->ps[1].tex)
		glGenTextures(1, &aw->ps[1].tex);
	glBindTexture(GL_TEXTURE_2D, aw->ps[1].tex);

	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 32, 32, 0,
				 GL_RGBA, GL_UNSIGNED_BYTE, fireTex);
	glBindTexture(GL_TEXTURE_2D, 0);

	aw->animFireDirection = getAnimationDirection
			(w, as->opt[ANIM_SCREEN_OPTION_FIRE_DIRECTION].value.s, FALSE);

	if (as->opt[ANIM_SCREEN_OPTION_FIRE_CONSTANT_SPEED].value.b)
	{
		aw->animTotalTime *= WIN_H(w) / 500.0;
		aw->animRemainingTime *= WIN_H(w) / 500.0;
	}
}

static void
fxBurnGenNewFire(CompScreen * s, ParticleSystem * ps, int x, int y,
				 int width, int height, float size, float time)
{
	ANIM_SCREEN(s);

	float max_new =
			ps->numParticles * (time / 50) * (1.05 -
											  as->
											  opt
											  [ANIM_SCREEN_OPTION_FIRE_LIFE].
											  value.f);
	int i;
	Particle *part;
	float rVal;

	for (i = 0; i < ps->numParticles && max_new > 0; i++)
	{
		part = &ps->particles[i];
		if (part->life <= 0.0f)
		{
			// give gt new life
			rVal = (float)(random() & 0xff) / 255.0;
			part->life = 1.0f;
			part->fade = (rVal * (1 - as->opt[ANIM_SCREEN_OPTION_FIRE_LIFE].value.f)) + (0.2f * (1.01 - as->opt[ANIM_SCREEN_OPTION_FIRE_LIFE].value.f));	// Random Fade Value

			// set size
			part->width = as->opt[ANIM_SCREEN_OPTION_FIRE_SIZE].value.f;
			part->height =
					as->opt[ANIM_SCREEN_OPTION_FIRE_SIZE].value.f * 1.5;
			rVal = (float)(random() & 0xff) / 255.0;
			part->w_mod = size * rVal;
			part->h_mod = size * rVal;

			// choose random position
			rVal = (float)(random() & 0xff) / 255.0;
			part->x = x + ((width > 1) ? (rVal * width) : 0);
			rVal = (float)(random() & 0xff) / 255.0;
			part->y = y + ((height > 1) ? (rVal * height) : 0);
			part->z = 0.0;
			part->xo = part->x;
			part->yo = part->y;
			part->zo = part->z;

			// set speed and direction
			rVal = (float)(random() & 0xff) / 255.0;
			part->xi = ((rVal * 20.0) - 10.0f);
			rVal = (float)(random() & 0xff) / 255.0;
			part->yi = ((rVal * 20.0) - 15.0f);
			part->zi = 0.0f;
			rVal = (float)(random() & 0xff) / 255.0;

			if (as->opt[ANIM_SCREEN_OPTION_FIRE_MYSTICAL].value.b)
			{
				// Random colors! (aka Mystical Fire)
				rVal = (float)(random() & 0xff) / 255.0;
				part->r = rVal;
				rVal = (float)(random() & 0xff) / 255.0;
				part->g = rVal;
				rVal = (float)(random() & 0xff) / 255.0;
				part->b = rVal;
			}
			else
			{
				// set color ABAB as->opt[ANIM_SCREEN_OPTION_FIRE_COLOR].value.f
				part->r =
						(float)as->
						opt[ANIM_SCREEN_OPTION_FIRE_COLOR].
						value.c[0] / 0xffff -
						(rVal / 1.7 *
						 (float)as->
						 opt[ANIM_SCREEN_OPTION_FIRE_COLOR].value.c[0] /
						 0xffff);
				part->g =
						(float)as->opt[ANIM_SCREEN_OPTION_FIRE_COLOR].value.
						c[1] / 0xffff -
						(rVal / 1.7 *
						 (float)as->opt[ANIM_SCREEN_OPTION_FIRE_COLOR].value.
						 c[1] / 0xffff);
				part->b =
						(float)as->opt[ANIM_SCREEN_OPTION_FIRE_COLOR].value.
						c[2] / 0xffff -
						(rVal / 1.7 *
						 (float)as->opt[ANIM_SCREEN_OPTION_FIRE_COLOR].value.
						 c[2] / 0xffff);
			}
			// set transparancy
			part->a =
					(float)as->opt[ANIM_SCREEN_OPTION_FIRE_COLOR].
					value.c[3] / 0xffff;

			// set gravity
			part->xg = (part->x < part->xo) ? 1.0 : -1.0;
			part->yg = -3.0f;
			part->zg = 0.0f;

			ps->active = TRUE;
			max_new -= 1;
		}
		else
		{
			part->xg = (part->x < part->xo) ? 1.0 : -1.0;
		}
	}

}

static void
fxBurnGenNewSmoke(CompScreen * s, ParticleSystem * ps, int x, int y,
				  int width, int height, float size, float time)
{
	ANIM_SCREEN(s);

	float max_new =
			ps->numParticles * (time / 50) * (1.05 -
											  as->
											  opt
											  [ANIM_SCREEN_OPTION_FIRE_LIFE].
											  value.f);
	int i;
	Particle *part;
	float rVal;

	for (i = 0; i < ps->numParticles && max_new > 0; i++)
	{
		part = &ps->particles[i];
		if (part->life <= 0.0f)
		{
			// give gt new life
			rVal = (float)(random() & 0xff) / 255.0;
			part->life = 1.0f;
			part->fade = (rVal * (1 - as->opt[ANIM_SCREEN_OPTION_FIRE_LIFE].value.f)) + (0.2f * (1.01 - as->opt[ANIM_SCREEN_OPTION_FIRE_LIFE].value.f));	// Random Fade Value

			// set size
			part->width =
					as->opt[ANIM_SCREEN_OPTION_FIRE_SIZE].value.f * size * 5;
			part->height =
					as->opt[ANIM_SCREEN_OPTION_FIRE_SIZE].value.f * size * 5;
			rVal = (float)(random() & 0xff) / 255.0;
			part->w_mod = -0.8;
			part->h_mod = -0.8;

			// choose random position
			rVal = (float)(random() & 0xff) / 255.0;
			part->x = x + ((width > 1) ? (rVal * width) : 0);
			rVal = (float)(random() & 0xff) / 255.0;
			part->y = y + ((height > 1) ? (rVal * height) : 0);
			part->z = 0.0;
			part->xo = part->x;
			part->yo = part->y;
			part->zo = part->z;

			// set speed and direction
			rVal = (float)(random() & 0xff) / 255.0;
			part->xi = ((rVal * 20.0) - 10.0f);
			rVal = (float)(random() & 0xff) / 255.0;
			part->yi = (rVal + 0.2) * -size;
			part->zi = 0.0f;

			// set color
			rVal = (float)(random() & 0xff) / 255.0;
			part->r = rVal / 4.0;
			part->g = rVal / 4.0;
			part->b = rVal / 4.0;
			rVal = (float)(random() & 0xff) / 255.0;
			part->a = 0.5 + (rVal / 2.0);

			// set gravity
			part->xg = (part->x < part->xo) ? size : -size;
			part->yg = -size;
			part->zg = 0.0f;

			ps->active = TRUE;
			max_new -= 1;
		}
		else
		{
			part->xg = (part->x < part->xo) ? size : -size;
		}
	}

}

static void fxBurnModelStep(CompScreen * s, CompWindow * w, float time)
{
	int steps;

	ANIM_SCREEN(s);
	ANIM_WINDOW(w);

	Model *model = aw->model;

	Bool smoke = as->opt[ANIM_SCREEN_OPTION_FIRE_SMOKE].value.b;

	float timestep = (s->slowAnimations ? 2 :	// For smooth slow-mo (refer to display.c)
					  as->opt[ANIM_SCREEN_OPTION_TIME_STEP_INTENSE].value.i);
	float old = 1 - (aw->animRemainingTime) / (aw->animTotalTime);
	float stepSize;

	model->remainderSteps += time / timestep;
	steps = floor(model->remainderSteps);
	model->remainderSteps -= steps;
	if (!steps && aw->animRemainingTime < aw->animTotalTime)
		return;
	steps = MAX(1, steps);

	aw->animRemainingTime -= timestep;
	if (aw->animRemainingTime <= 0)
		aw->animRemainingTime = 0;	// avoid sub-zero values
	float new = 1 - (aw->animRemainingTime) / (aw->animTotalTime);

	stepSize = new - old;

	if (aw->curWindowEvent == WindowEventCreate ||
		aw->curWindowEvent == WindowEventUnminimize ||
		aw->curWindowEvent == WindowEventUnshade)
	{
		old = 1 - old;
		new = 1 - new;
	}

	if (!aw->drawRegion)
		aw->drawRegion = XCreateRegion();
	if (aw->animRemainingTime > 0)
	{
		XRectangle rect;

		switch (aw->animFireDirection)
		{
		case AnimDirectionUp:
			rect.x = 0;
			rect.y = 0;
			rect.width = WIN_W(w);
			rect.height = WIN_H(w) - (old * WIN_H(w));
			break;
		case AnimDirectionRight:
			rect.x = (old * WIN_W(w));
			rect.y = 0;
			rect.width = WIN_W(w) - (old * WIN_W(w));
			rect.height = WIN_H(w);
			break;
		case AnimDirectionLeft:
			rect.x = 0;
			rect.y = 0;
			rect.width = WIN_W(w) - (old * WIN_W(w));
			rect.height = WIN_H(w);
			break;
		case AnimDirectionDown:
		default:
			rect.x = 0;
			rect.y = (old * WIN_H(w));
			rect.width = WIN_W(w);
			rect.height = WIN_H(w) - (old * WIN_H(w));
			break;
		}
		XUnionRectWithRegion(&rect, getEmptyRegion(), aw->drawRegion);
	}
	else
	{
		XUnionRegion(getEmptyRegion(), getEmptyRegion(), aw->drawRegion);
	}
	if (new != 0)
		aw->useDrawRegion = TRUE;
	else
		aw->useDrawRegion = FALSE;

	if (aw->animRemainingTime > 0 && aw->ps)
	{
		switch (aw->animFireDirection)
		{
		case AnimDirectionUp:
			if (smoke)
				fxBurnGenNewSmoke(s, &aw->ps[0], WIN_X(w),
								  WIN_Y(w) +
								  ((1 - old) * WIN_H(w)),
								  WIN_W(w), 1, WIN_W(w) / 40.0, time);
			fxBurnGenNewFire(s, &aw->ps[1], WIN_X(w),
							 WIN_Y(w) + ((1 - old) * WIN_H(w)),
							 WIN_W(w), (stepSize) * WIN_H(w),
							 WIN_W(w) / 40.0, time);
			break;
		case AnimDirectionLeft:
			if (smoke)
				fxBurnGenNewSmoke(s, &aw->ps[0],
								  WIN_X(w) +
								  ((1 - old) * WIN_W(w)),
								  WIN_Y(w),
								  (stepSize) * WIN_W(w),
								  WIN_H(w), WIN_H(w) / 40.0, time);
			fxBurnGenNewFire(s, &aw->ps[1],
							 WIN_X(w) + ((1 - old) * WIN_W(w)),
							 WIN_Y(w), (stepSize) * WIN_W(w),
							 WIN_H(w), WIN_H(w) / 40.0, time);
			break;
		case AnimDirectionRight:
			if (smoke)
				fxBurnGenNewSmoke(s, &aw->ps[0],
								  WIN_X(w) +
								  (old * WIN_W(w)),
								  WIN_Y(w),
								  (stepSize) * WIN_W(w),
								  WIN_H(w), WIN_H(w) / 40.0, time);
			fxBurnGenNewFire(s, &aw->ps[1],
							 WIN_X(w) + (old * WIN_W(w)),
							 WIN_Y(w), (stepSize) * WIN_W(w),
							 WIN_H(w), WIN_H(w) / 40.0, time);
			break;
		case AnimDirectionDown:
		default:
			if (smoke)
				fxBurnGenNewSmoke(s, &aw->ps[0], WIN_X(w),
								  WIN_Y(w) +
								  (old * WIN_H(w)),
								  WIN_W(w), 1, WIN_W(w) / 40.0, time);
			fxBurnGenNewFire(s, &aw->ps[1], WIN_X(w),
							 WIN_Y(w) + (old * WIN_H(w)),
							 WIN_W(w), (stepSize) * WIN_H(w),
							 WIN_W(w) / 40.0, time);
			break;
		}

	}
	if (aw->animRemainingTime <= 0 && aw->numPs
		&& (aw->ps[0].active || aw->ps[1].active))
		aw->animRemainingTime = timestep;

	if (!aw->numPs)
	{
		if (aw->ps)
		{
			finiParticles(aw->ps);
			free(aw->ps);
			aw->ps = NULL;
		}
		return;					// FIXME - is this correct behaviour?
	}

	int i;
	Particle *part;

	for (i = 0;
		 i < aw->ps[0].numParticles && aw->animRemainingTime > 0
		 && smoke; i++)
	{
		part = &aw->ps[0].particles[i];
		part->xg = (part->x < part->xo) ? WIN_W(w) / 40.0 : -WIN_W(w) / 40.0;
	}
	aw->ps[0].x = WIN_X(w);
	aw->ps[0].y = WIN_Y(w);

	for (i = 0; i < aw->ps[1].numParticles && aw->animRemainingTime > 0; i++)
	{
		part = &aw->ps[1].particles[i];
		part->xg = (part->x < part->xo) ? 1.0 : -1.0;
	}
	aw->ps[1].x = WIN_X(w);
	aw->ps[1].y = WIN_Y(w);

	modelCalcBounds(model);
}

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

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

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


// =====================  Effect: Beam Up  =========================

static void fxBeamUpInit(CompScreen * s, CompWindow * w)
{
	int particles = WIN_W(w);

	fxFadeInit(s, w);
	ANIM_WINDOW(w);
	ANIM_SCREEN(s);
	if (!aw->numPs)
	{
		aw->ps = calloc(1, 2 * sizeof(ParticleSystem));
		if (!aw->ps)
		{
			postAnimationCleanup(w, TRUE);
			return;
		}
		aw->numPs = 2;
	}
	initParticles(particles / 10, &aw->ps[0]);
	initParticles(particles, &aw->ps[1]);
	aw->ps[1].slowdown = as->opt[ANIM_SCREEN_OPTION_BEAMUP_SLOWDOWN].value.f;
	aw->ps[1].darken = 0.5;
	aw->ps[1].blendMode = GL_ONE;

	aw->ps[0].slowdown =
			as->opt[ANIM_SCREEN_OPTION_BEAMUP_SLOWDOWN].value.f / 2.0;
	aw->ps[0].darken = 0.0;
	aw->ps[0].blendMode = GL_ONE_MINUS_SRC_ALPHA;

	if (!aw->ps[0].tex)
		glGenTextures(1, &aw->ps[0].tex);
	glBindTexture(GL_TEXTURE_2D, aw->ps[0].tex);

	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 32, 32, 0,
				 GL_RGBA, GL_UNSIGNED_BYTE, fireTex);
	glBindTexture(GL_TEXTURE_2D, 0);

	if (!aw->ps[1].tex)
		glGenTextures(1, &aw->ps[1].tex);
	glBindTexture(GL_TEXTURE_2D, aw->ps[1].tex);

	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 32, 32, 0,
				 GL_RGBA, GL_UNSIGNED_BYTE, fireTex);
	glBindTexture(GL_TEXTURE_2D, 0);

}

static void
fxBeamUpGenNewFire(CompScreen * s, ParticleSystem * ps, int x, int y,
				   int width, int height, float size, float time)
{
	ANIM_SCREEN(s);

	ps->numParticles =
			width / as->opt[ANIM_SCREEN_OPTION_BEAMUP_SPACING].value.i;
	float max_new =
			ps->numParticles * (time / 50) * (1.05 -
											  as->
											  opt
											  [ANIM_SCREEN_OPTION_BEAMUP_LIFE].
											  value.f);
	int i;
	Particle *part;
	float rVal;

	for (i = 0; i < ps->numParticles && max_new > 0; i++)
	{
		part = &ps->particles[i];
		if (part->life <= 0.0f)
		{
			// give gt new life
			rVal = (float)(random() & 0xff) / 255.0;
			part->life = 1.0f;
			part->fade = (rVal * (1 - as->opt[ANIM_SCREEN_OPTION_BEAMUP_LIFE].value.f)) + (0.2f * (1.01 - as->opt[ANIM_SCREEN_OPTION_BEAMUP_LIFE].value.f));	// Random Fade Value

			// set size
			part->width =
					2.5 * as->opt[ANIM_SCREEN_OPTION_BEAMUP_SIZE].value.f;
			part->height = height;
			part->w_mod = size * 0.2;
			part->h_mod = size * 0.02;

			// choose random x position
			rVal = (float)(random() & 0xff) / 255.0;
			part->x = x + ((width > 1) ? (rVal * width) : 0);
			part->y = y;
			part->z = 0.0;
			part->xo = part->x;
			part->yo = part->y;
			part->zo = part->z;

			// set speed and direction
			part->xi = 0.0f;
			part->yi = 0.0f;
			part->zi = 0.0f;

			// set color ABAB as->opt[ANIM_SCREEN_OPTION_BEAMUP_COLOR].value.f
			part->r =
					(float)as->
					opt[ANIM_SCREEN_OPTION_BEAMUP_COLOR].value.
					c[0] / 0xffff -
					(rVal / 1.7 *
					 (float)as->
					 opt[ANIM_SCREEN_OPTION_BEAMUP_COLOR].value.c[0] /
					 0xffff);
			part->g =
					(float)as->opt[ANIM_SCREEN_OPTION_BEAMUP_COLOR].value.
					c[1] / 0xffff -
					(rVal / 1.7 *
					 (float)as->opt[ANIM_SCREEN_OPTION_BEAMUP_COLOR].value.
					 c[1] / 0xffff);
			part->b =
					(float)as->opt[ANIM_SCREEN_OPTION_BEAMUP_COLOR].value.
					c[2] / 0xffff -
					(rVal / 1.7 *
					 (float)as->opt[ANIM_SCREEN_OPTION_BEAMUP_COLOR].value.
					 c[2] / 0xffff);;
			part->a =
					(float)as->opt[ANIM_SCREEN_OPTION_BEAMUP_COLOR].value.
					c[3] / 0xffff;

			// set gravity
			part->xg = 0.0f;
			part->yg = 0.0f;
			part->zg = 0.0f;

			ps->active = TRUE;
			max_new -= 1;
		}
		else
		{
			part->xg = (part->x < part->xo) ? 1.0 : -1.0;
		}
	}

}

static void fxBeamUpModelStep(CompScreen * s, CompWindow * w, float time)
{
	int steps;
	int creating = 0;

	fxFadeModelStep(s, w, time);

	ANIM_SCREEN(s);
	ANIM_WINDOW(w);

	Model *model = aw->model;

	float timestep = (s->slowAnimations ? 2 :	// For smooth slow-mo (refer to display.c)
					  as->opt[ANIM_SCREEN_OPTION_TIME_STEP_INTENSE].value.i);
	float old = 1 - (aw->animRemainingTime) / (aw->animTotalTime);
	float stepSize;

	model->remainderSteps += time / timestep;
	steps = floor(model->remainderSteps);
	model->remainderSteps -= steps;
	if (!steps && aw->animRemainingTime < aw->animTotalTime)
		return;
	steps = MAX(1, steps);

	aw->animRemainingTime -= timestep;
	if (aw->animRemainingTime <= 0)
		aw->animRemainingTime = 0;	// avoid sub-zero values
	float new = 1 - (aw->animRemainingTime) / (aw->animTotalTime);

	stepSize = new - old;

	if (aw->curWindowEvent == WindowEventCreate ||
		aw->curWindowEvent == WindowEventUnminimize ||
		aw->curWindowEvent == WindowEventUnshade)
	{
		old = 1 - old;
		new = 1 - new;
		creating = 1;
	}

	if (!aw->drawRegion)
		aw->drawRegion = XCreateRegion();
	if (aw->animRemainingTime > 0)
	{
		XRectangle rect;

		rect.x = ((old / 2) * WIN_W(w));
		rect.width = WIN_W(w) - (old * WIN_W(w));
		rect.y = ((old / 2) * WIN_H(w));
		rect.height = WIN_H(w) - (old * WIN_H(w));
		XUnionRectWithRegion(&rect, getEmptyRegion(), aw->drawRegion);
	}
	else
	{
		XUnionRegion(getEmptyRegion(), getEmptyRegion(), aw->drawRegion);
	}
	if (new != 0)
		aw->useDrawRegion = TRUE;
	else
		aw->useDrawRegion = FALSE;

	if (aw->animRemainingTime > 0 && aw->ps)
	{
		fxBeamUpGenNewFire(s, &aw->ps[1], WIN_X(w),
						   WIN_Y(w) + (WIN_H(w) / 2), WIN_W(w),
						   creating ? WIN_H(w) -
						   (old / 2 * WIN_H(w)) : (WIN_H(w) -
												   (old *
													WIN_H(w))),
						   WIN_W(w) / 40.0, time);

	}
	if (aw->animRemainingTime <= 0 && aw->numPs
		&& (aw->ps[0].active || aw->ps[1].active))
		aw->animRemainingTime = timestep;

	if (!aw->numPs)
	{
		if (aw->ps)
		{
			finiParticles(aw->ps);
			free(aw->ps);
			aw->ps = NULL;
		}
		return;					// FIXME - is this correct behaviour?
	}

	int i;
	Particle *part;

	aw->ps[0].x = WIN_X(w);
	aw->ps[0].y = WIN_Y(w);

	for (i = 0; i < aw->ps[1].numParticles && aw->animRemainingTime > 0; i++)
	{
		part = &aw->ps[1].particles[i];
		part->xg = (part->x < part->xo) ? 1.0 : -1.0;
	}
	aw->ps[1].x = WIN_X(w);
	aw->ps[1].y = WIN_Y(w);

	modelCalcBounds(model);
}

static void
fxBeamupUpdateWindowAttrib(AnimScreen * as,
						   AnimWindow * aw, WindowPaintAttrib * wAttrib)
{
	float forwardProgress =
			1 - (aw->animRemainingTime - aw->timestep) /
			(aw->animTotalTime - aw->timestep);
	forwardProgress = MIN(forwardProgress, 1);
	forwardProgress = MAX(forwardProgress, 0);

	if (aw->curWindowEvent == WindowEventCreate ||
		aw->curWindowEvent == WindowEventUnminimize)
	{
		forwardProgress = forwardProgress * forwardProgress;
		forwardProgress = forwardProgress * forwardProgress;
		forwardProgress = 1 - forwardProgress;
	}

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


// =====================  Effect: 3D Spread  =========================

// Frees up polygon objects in pset
static void freePolygonObjects(PolygonSet * pset)
{
	PolygonObject *p = pset->polygons;

	if (!p)
	{
		pset->nPolygons = 0;
		return;
	}
	int i;

	for (i = 0; i < pset->nPolygons; i++, p++)
	{
		if (p->nVertices > 0)
		{
			if (p->vertices)
				free(p->vertices);
			if (p->sideIndices)
				free(p->sideIndices);
			if (p->normals)
				free(p->normals);
			p->vertices = 0;
			p->sideIndices = 0;
			p->normals = 0;
			p->nVertices = 0;
		}
		/*if (p->nShadowQuads > 0)
		   {
		   if (p->shadowVertices)
		   free(p->shadowVertices);
		   if (p->shadowTexCoords)
		   free(p->shadowTexCoords);
		   p->shadowVertices = 0;
		   p->shadowTexCoords = 0;
		   p->nShadowQuads = 0;
		   } */
		p->nSides = 0;
	}
	free(pset->polygons);
	pset->polygons = 0;
	pset->nPolygons = 0;
}

// Frees up intersecting polygon info of PolygonSet clips
static void freeClipsPolygons(PolygonSet * pset)
{
	int k;

	for (k = 0; k < pset->clipCapacity; k++)
	{
		if (pset->clips[k].intersectingPolygons)
		{
			free(pset->clips[k].intersectingPolygons);
			pset->clips[k].intersectingPolygons = 0;
		}
		if (pset->clips[k].polygonVertexTexCoords)
		{
			free(pset->clips[k].polygonVertexTexCoords);
			pset->clips[k].polygonVertexTexCoords = 0;
		}
		pset->clips[k].nIntersectingPolygons = 0;
	}
}

// Frees up the whole polygon set
static void freePolygonSet(AnimWindow * aw)
{
	PolygonSet *pset = aw->polygonSet;

	freePolygonObjects(pset);
	if (pset->clipCapacity > 0)
	{
		freeClipsPolygons(pset);
		free(pset->clips);
		pset->clips = 0;
		pset->nClips = 0;
		pset->firstNondrawnClip = 0;
		pset->clipCapacity = 0;
	}
	free(pset);
	aw->polygonSet = 0;
}

/*
// Tessellates window into extruded rectangular objects
// in a brick wall formation
static Bool
tessellateIntoBricks(CompWindow * w,
                     int gridSizeX, int gridSizeY, float thickness)
{
}

// Tessellates window into extruded hexagon objects
static Bool
tessellateIntoHexagons(CompWindow * w,
                     int gridSizeX, int gridSizeY, float thickness)
{
}

// Tessellates window into Voronoi segments
static Bool
tessellateVoronoi(CompWindow * w,
                  float thickness)
{
}
*/

// Tessellates window into extruded rectangular objects
static Bool
tessellateIntoRectangles(CompWindow * w,
						 int gridSizeX, int gridSizeY, float thickness)
{
	ANIM_WINDOW(w);

	PolygonSet *pset = aw->polygonSet;

	if (!pset)
		return FALSE;

	int winLimitsX;				// boundaries of polygon tessellation
	int winLimitsY;
	int winLimitsW;
	int winLimitsH;

	if (pset->includeShadows)
	{
		winLimitsX = WIN_X(w);
		winLimitsY = WIN_Y(w);
		winLimitsW = WIN_W(w) - 1; // avoid artifact on right edge
		winLimitsH = WIN_H(w);
	}
	else
	{
		winLimitsX = BORDER_X(w);
		winLimitsY = BORDER_Y(w);
		winLimitsW = BORDER_W(w);
		winLimitsH = BORDER_H(w);
	}
	float minRectSize = 20;
	float rectW = winLimitsW / (float)gridSizeX;
	float rectH = winLimitsH / (float)gridSizeY;

	if (rectW < minRectSize)
		gridSizeX = winLimitsW / minRectSize;	// int div.
	if (rectH < minRectSize)
		gridSizeY = winLimitsH / minRectSize;	// int div.

	if (pset->nPolygons != gridSizeX * gridSizeY)
	{
		if (pset->nPolygons > 0)
			freePolygonObjects(pset);

		pset->nPolygons = gridSizeX * gridSizeY;

		pset->polygons = calloc(1, sizeof(PolygonObject) * pset->nPolygons);
		if (!pset->polygons)
		{
			fprintf(stderr, "%s: Not enough memory at line %d!\n",
					__FILE__, __LINE__);
			pset->nPolygons = 0;
			return FALSE;
		}
	}

	thickness /= w->screen->width;
	pset->thickness = thickness;
	pset->nTotalFrontVertices = 0;

	float cellW = (float)winLimitsW / gridSizeX;
	float cellH = (float)winLimitsH / gridSizeY;
	float halfW = cellW / 2;
	float halfH = cellH / 2;

	float halfThick = pset->thickness / 2;
	PolygonObject *p = pset->polygons;
	int x, y;

	//float vec = 1 / sqrt(3); // vector component for normals

	for (y = 0; y < gridSizeY; y++)
	{
		float posY = winLimitsY + cellH * (y + 0.5);

		for (x = 0; x < gridSizeX; x++, p++)
		{
			p->centerPos.x = p->centerPosStart.x =
					winLimitsX + cellW * (x + 0.5);
			p->centerPos.y = p->centerPosStart.y = posY;
			p->centerPos.z = p->centerPosStart.z = -halfThick;
			p->rotAngle = p->rotAngleStart = 0;

			p->centerRelPos.x = (x + 0.5) / gridSizeX;
			p->centerRelPos.y = (y + 0.5) / gridSizeY;

			p->nSides = 4;
			p->nVertices = 2 * 4;
			pset->nTotalFrontVertices += 4;

			// 4 front, 4 back vertices
			if (!p->vertices)
			{
				p->vertices = calloc(1, sizeof(GLfloat) * 8 * 3);
			}
			//if (!p->vertexOnEdge)
			//  p->vertexOnEdge = calloc (1, sizeof (int) * p->nSides);
			//p->vertexTexCoords4Clips = calloc (1, sizeof (GLfloat) * 4 * 2 * 2);
			if (!p->vertices)	// || !p->vertexOnEdge)// || !p->vertexTexCoords4Clips)
			{
				fprintf(stderr, "%s: Not enough memory at line %d!\n",
						__FILE__, __LINE__);
				freePolygonObjects(pset);
				return FALSE;
			}

			GLfloat *pv = p->vertices;

			// Determine 4 front vertices in ccw direction
			pv[0] = -halfW;
			pv[1] = -halfH;
			pv[2] = halfThick;

			pv[3] = -halfW;
			pv[4] = halfH;
			pv[5] = halfThick;

			pv[6] = halfW;
			pv[7] = halfH;
			pv[8] = halfThick;

			pv[9] = halfW;
			pv[10] = -halfH;
			pv[11] = halfThick;

			// Determine 4 back vertices in cw direction
			pv[12] = halfW;
			pv[13] = -halfH;
			pv[14] = -halfThick;

			pv[15] = halfW;
			pv[16] = halfH;
			pv[17] = -halfThick;

			pv[18] = -halfW;
			pv[19] = halfH;
			pv[20] = -halfThick;

			pv[21] = -halfW;
			pv[22] = -halfH;
			pv[23] = -halfThick;

			// 10 indices for 4 sides (for quad strip)
			if (!p->sideIndices)
			{
				//p->sideIndices = calloc(1, sizeof(GLushort) * 2 * (4 + 1));
				p->sideIndices = calloc(1, sizeof(GLushort) * 4 * 4);
			}
			if (!p->sideIndices)
			{
				fprintf(stderr, "%s: Not enough memory at line %d!\n",
						__FILE__, __LINE__);
				freePolygonObjects(pset);
				return FALSE;
			}

			GLushort *ind = p->sideIndices;

			/*
			   ind[0] = 0;
			   ind[1] = 7;
			   ind[2] = 1;
			   ind[3] = 6;
			   ind[4] = 2;
			   ind[5] = 5;
			   ind[6] = 3;
			   ind[7] = 4;
			   ind[8] = 0;
			   ind[9] = 7;
			 */
			int id = 0;

			ind[id++] = 0;
			ind[id++] = 7;
			ind[id++] = 6;
			ind[id++] = 1;

			ind[id++] = 1;
			ind[id++] = 6;
			ind[id++] = 5;
			ind[id++] = 2;

			ind[id++] = 2;
			ind[id++] = 5;
			ind[id++] = 4;
			ind[id++] = 3;

			ind[id++] = 3;
			ind[id++] = 4;
			ind[id++] = 7;
			ind[id++] = 0;

			// 4 front, 4 back vertex normals
			if (!p->normals)
			{
				p->normals = calloc(1, sizeof(GLfloat) * (2 + 4) * 3);
			}
			if (!p->normals)
			{
				fprintf(stderr, "%s: Not enough memory at line %d!\n",
						__FILE__, __LINE__);
				freePolygonObjects(pset);
				return FALSE;
			}

			// Surface normals
			GLfloat *nor = p->normals;

			// Front
			nor[0] = 0;
			nor[1] = 0;
			nor[2] = -1;

			// Back
			nor[3] = 0;
			nor[4] = 0;
			nor[5] = 1;

			// Sides
			nor[6] = -1;
			nor[7] = 0;
			nor[8] = 0;

			nor[9] = 0;
			nor[10] = 1;
			nor[11] = 0;

			nor[12] = 1;
			nor[13] = 0;
			nor[14] = 0;

			nor[15] = 0;
			nor[16] = -1;
			nor[17] = 0;

			// Determine bounding box (to test intersection with clips)
			p->boundingBox.x1 = -halfW + p->centerPos.x;
			p->boundingBox.y1 = -halfH + p->centerPos.y;
			p->boundingBox.x2 = ceil(halfW + p->centerPos.x);
			p->boundingBox.y2 = ceil(halfH + p->centerPos.y);
			/*
			   // Determine edge/corner status
			   if (x == 0)
			   {
			   p->nShadowQuads++;
			   if (y == 0)
			   p->vertexOnEdge[0] = 5;
			   if (y == gridSizeY - 1)
			   p->nShadowQuads++;
			   }
			   if (x == gridSizeX - 1)
			   {
			   p->nShadowQuads++;
			   if (y == 0)
			   p->nShadowQuads++;
			   if (y == gridSizeY - 1)
			   p->nShadowQuads++;
			   }
			   if (y == 0)
			   p->nShadowQuads++;
			   if (y == gridSizeY - 1)
			   p->nShadowQuads++; */
		}
	}
	return TRUE;
}

static void
polygonsStoreClips(CompScreen * s, CompWindow * w,
				   int nClip, BoxPtr pClip, int nMatrix, CompMatrix * matrix)
{
	ANIM_WINDOW(w);

	PolygonSet *pset = aw->polygonSet;

	// if polygon set is not valid or effect is not 3D (glide w/thickness=0)
	if (!pset)
		return;

	// only draw windows on current viewport
	if (w->attrib.x > s->width || w->attrib.x + w->width < 0 ||
		w->attrib.y > s->height || w->attrib.y + w->height < 0 ||
		(aw->lastKnownCoords.x != NOT_INITIALIZED &&
		 (aw->lastKnownCoords.x != w->attrib.x ||
		  aw->lastKnownCoords.y != w->attrib.y)))
	{
		return;
		// since this is not the viewport the window was drawn
		// just before animation started
	}

	Bool dontStoreClips = TRUE;

	// If this clip doesn't match the corresponding stored clip,
	// clear the stored clips from this point (aw->nClipsPassed)
	// to the end and store the new ones instead.

	if (aw->nClipsPassed < pset->nClips) // if we have clips stored earlier
	{
		Clip4Polygons *c = pset->clips + aw->nClipsPassed;
		// the stored clip at position aw->nClipsPassed

		// if either clip coordinates or texture matrix is different
		if (memcmp(pClip, &c->box, sizeof(Box)) ||
			memcmp(matrix, &c->texMatrix, sizeof(CompMatrix)))
		{
			// get rid of the clips from here (aw->nClipsPassed) to the end
			pset->nClips = aw->nClipsPassed;
			dontStoreClips = FALSE;
		}
	}
	else
		dontStoreClips = FALSE;

	if (dontStoreClips)
	{
		aw->nClipsPassed += nClip;
		return;
	}
	// For each clip passed to this function
	for (; nClip--; pClip++, aw->nClipsPassed++)
	{
		// New clip

		if (!ensureLargerClipCapacity(pset))
		{
			fprintf(stderr, "%s: Not enough memory at line %d!\n",
					__FILE__, __LINE__);
			return;
		}

		Clip4Polygons *newClip = &pset->clips[pset->nClips];

		newClip->id = aw->nClipsPassed;
		memcpy(&newClip->box, pClip, sizeof(Box));
		memcpy(&newClip->texMatrix, matrix, sizeof(CompMatrix));
		// nMatrix is not used for now
		// (i.e. only first texture matrix is considered)

		// avoid clipping along window edge
		// for the "window contents" clip
		if (pClip->x1 == BORDER_X(w) &&
		    pClip->y1 == BORDER_Y(w) &&
			pClip->x2 == BORDER_X(w) + BORDER_W(w) &&
			pClip->y2 == BORDER_Y(w) + BORDER_H(w))
		{
			newClip->boxf.x1 = pClip->x1 - 0.1f;
			newClip->boxf.y1 = pClip->y1 - 0.1f;
			newClip->boxf.x2 = pClip->x2 + 0.1f;
			newClip->boxf.y2 = pClip->y2 + 0.1f;
		}
		else
		{
			newClip->boxf.x1 = pClip->x1;
			newClip->boxf.y1 = pClip->y1;
			newClip->boxf.x2 = pClip->x2;
			newClip->boxf.y2 = pClip->y2;
		}

		pset->nClips++;
		aw->clipsUpdated = TRUE;

		/*
		   // Look for a container / contained clip to minimize # of clips.
		   // Go backward, since such a clip is likely to be one of the last ones.
		   // Go until you hit the clips corresponding to the previous texture.
		   int i;
		   for (i=pset->nClips-1; i>=pset->firstClipWithNoTex; i--)
		   {
		   Clip4Polygons *clip = &pset->clips[i];

		   // if tex matrices are different
		   if (clip->texMatrix.xx != matrix->xx ||
		   clip->texMatrix.yy != matrix->yy ||
		   clip->texMatrix.x0 != matrix->x0 ||
		   clip->texMatrix.y0 != matrix->y0)
		   continue;

		   // if an old clip contains the new clip
		   if (x1 >= clip->box.x1 && y1 >= clip->box.y1 &&
		   x2 <= clip->box.x2 && y2 <= clip->box.y2)
		   {
		   newClipNo = i;
		   updateCoords = FALSE;
		   break;
		   }
		   // if the new clip contains an old clip
		   if (x1 <= clip->box.x1 && y1 <= clip->box.y1 &&
		   x2 >= clip->box.x2 && y2 >= clip->box.y2)
		   {
		   // update the old clip in this case
		   newClipNo = i;
		   break;
		   }
		   }
		   if (pset->firstClipWithNoTex > 0)
		   {
		   // Go through clip's of earlier textures to look for
		   // an identical clip. If such clips are found, their
		   // texture should be updated with the latest one
		   for (i=pset->firstClipWithNoTex-1; i>=0; i--)
		   {
		   Clip4Polygons *clip = &pset->clips[i];

		   // if tex matrices are different
		   if (clip->texMatrix.xx != matrix->xx ||
		   clip->texMatrix.yy != matrix->yy ||
		   clip->texMatrix.x0 != matrix->x0 ||
		   clip->texMatrix.y0 != matrix->y0)
		   continue;

		   // if an old clip == the new clip
		   if (x1 == clip->box.x1 && y1 == clip->box.y1 &&
		   x2 == clip->box.x2 && y2 == clip->box.y2)
		   {
		   newClipNo = i;
		   updateCoords = FALSE;
		   clip->textureNo = aw->nTextures;
		   break;
		   }
		   }
		   }

		   if (newClipNo == pset->nClips && // if no such old clip is found
		   !ensureLargerClipCapacity (pset))
		   {
		   fprintf(stderr, "%s: Not enough memory at line %d!\n",
		   __FILE__, __LINE__);
		   return;
		   }
		   Clip4Polygons *newClip = &pset->clips[newClipNo];
		   if (updateCoords)
		   {
		   memcpy (&newClip->box, pClip, sizeof (Box));
		   memcpy (&newClip->texMatrix, matrix, sizeof (CompMatrix));
		   // nMatrix is not used for now
		   // (i.e. only first texture matrix is considered)
		   }
		   if (newClipNo == pset->nClips) // if new clip is being added
		   {
		   //printf("adding x1: %4d, y1: %4d, x2: %4d, y2: %4d\n",
		   //       pClip->x1, pClip->y1, pClip->x2, pClip->y2);

		   newClip->textureNo = aw->nTextures;
		   pset->nClips++;
		   } */
	}
}

// For each rectangular clip, this function finds polygons which
// have a bounding box that intersects the clip. For intersecting
// polygons, it computes the texture coordinates for the vertices
// of that polygon (to draw the clip texture).
static Bool processIntersectingPolygons(PolygonSet * pset)
{
	int j;

	for (j = pset->firstNondrawnClip; j < pset->nClips; j++)
	{
		Clip4Polygons *c = pset->clips + j;
		Box *cb = &c->box;
		int nFrontVerticesTilThisPoly = 0;

		c->nIntersectingPolygons = 0;

		// TODO: If it doesn't affect speed much, for each clip,
		// consider doing 2 passes, counting the intersecting polygons
		// in the 1st pass and allocating just enough space for those
		// polygons instead of all polygons in the 2nd pass.
		int i;

		for (i = 0; i < pset->nPolygons; i++)
		{
			PolygonObject *p = pset->polygons + i;

			Box *bb = &p->boundingBox;

			if (bb->x2 <= cb->x1)
				continue;		// no intersection
			if (bb->y2 <= cb->y1)
				continue;		// no intersection
			if (bb->x1 >= cb->x2)
				continue;		// no intersection
			if (bb->y1 >= cb->y2)
				continue;		// no intersection

			// There is intersection, add clip info

			if (!c->intersectingPolygons)
			{
				c->intersectingPolygons = calloc(1, sizeof(int) *
												 pset->nPolygons);
			}
			// allocate tex coords
			// 2 {x, y} * 2 {front, back} * <total # of polygon front vertices>
			if (!c->polygonVertexTexCoords)
			{
				c->polygonVertexTexCoords =
						calloc(1, sizeof(GLfloat) * 2 * 2 *
							   pset->nTotalFrontVertices);
			}
			if (!c->intersectingPolygons || !c->polygonVertexTexCoords)
			{
				fprintf(stderr, "%s: Not enough memory at line %d!\n",
						__FILE__, __LINE__);
				freeClipsPolygons(pset);
				return FALSE;
			}
			c->intersectingPolygons[c->nIntersectingPolygons] = i;

			int k;

			for (k = 0; k < p->nSides; k++)
			{
				float x = p->vertices[3 * k] +
						  p->centerPosStart.x;
				float y = p->vertices[3 * k + 1] +
						  p->centerPosStart.y;
				GLfloat tx;
				GLfloat ty;
				if (c->texMatrix.xy != 0.0f || c->texMatrix.yx != 0.0f)
				{	// "non-rect" coordinates
					tx = COMP_TEX_COORD_XY(&c->texMatrix, x, y);
					ty = COMP_TEX_COORD_YX(&c->texMatrix, x, y);
				}
				else
				{
					tx = COMP_TEX_COORD_X(&c->texMatrix, x);
					ty = COMP_TEX_COORD_Y(&c->texMatrix, y);
				}
				// for front vertices
				int ti = 2 * (2 * nFrontVerticesTilThisPoly + k);

				c->polygonVertexTexCoords[ti] = tx;
				c->polygonVertexTexCoords[ti + 1] = ty;

				// for back vertices
				ti = 2 * (2 * nFrontVerticesTilThisPoly +
						  (2 * p->nSides - 1 - k));
				c->polygonVertexTexCoords[ti] = tx;
				c->polygonVertexTexCoords[ti + 1] = ty;
			}
			c->nIntersectingPolygons++;
			nFrontVerticesTilThisPoly += p->nSides;
		}
	}

	return TRUE;
}

/*
static Bool computePolygonShadows(PolygonSet * pset)
{
	   PolygonObject *p = pset->polygons;
	   int i;
	   for (i=0; i<pset->nPolygons; i++, p++)
	   {
	   p->nShadowQuads = 0;

	   // If an edge polygon, determine shadow quads
	   // First count them
	   if (x == 0)
	   {
	   p->nShadowQuads++;
	   if (y == 0)
	   p->nShadowQuads++;
	   if (y == gridSizeY - 1)
	   p->nShadowQuads++;
	   }
	   if (x == gridSizeX - 1)
	   {
	   p->nShadowQuads++;
	   if (y == 0)
	   p->nShadowQuads++;
	   if (y == gridSizeY - 1)
	   p->nShadowQuads++;
	   }
	   if (y == 0)
	   p->nShadowQuads++;
	   if (y == gridSizeY - 1)
	   p->nShadowQuads++;

	   // Allocate them
	   p->shadowVertices = calloc (1, sizeof (GLfloat) * 3 *
	   4 * p->nShadowQuads);
	   if (!p->shadowVertices)
	   {
	   fprintf(stderr, "%s: Not enough memory at line %d!\n",
	   __FILE__, __LINE__);
	   freePolygonObjects (pset);
	   return FALSE;
	   }
	   // Store them
	   int n = 0;
	   GLfloat *v = p->shadowVertices;
	   if (x == 0)
	   {
	   // Left quad
	   v[12 * n]     = w->attrib.x - w->output.left;
	   v[12 * n + 1] = w->attrib.x - w->output.left;
	   v[12 * n + 2] = w->attrib.x - w->output.left;
	   n++;
	   if (y == 0)
	   n++;
	   if (y == gridSizeY - 1)
	   n++;
	   }
	   if (x == gridSizeX - 1)
	   {
	   n++;
	   if (y == 0)
	   n++;
	   if (y == gridSizeY - 1)
	   n++;
	   }
	   if (y == 0)
	   n++;
	   if (y == gridSizeY - 1)
	   n++;

	   p->nShadowQuads = n;
	   }
	return TRUE;
}
*/

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

	if (						// only draw windows on current viewport
		   w->attrib.x > s->width || w->attrib.x + w->width < 0 ||
		   w->attrib.y > s->height || w->attrib.y + w->height < 0 ||
		   (aw->lastKnownCoords.x != NOT_INITIALIZED &&
			(aw->lastKnownCoords.x != w->attrib.x ||
			 aw->lastKnownCoords.y != w->attrib.y)))
	{
		return;
		// since this is not the viewport the window was drawn
		// just before animation started
	}
	PolygonSet *pset = aw->polygonSet;

	// if polygon set is not valid or effect is not 3D (glide w/thickness=0)
	if (!pset)
		return;

	// TODO: Fix the source of the crash problem
	// (uninitialized lastClipInGroup)
	// instead of doing this uninitialized value check
	if (pset->firstNondrawnClip < 0 ||
		pset->firstNondrawnClip > pset->nClips ||
		(!aw->clipsUpdated &&
		 (pset->lastClipInGroup[aw->nDrawGeometryCalls - 1] < 0 ||
		  pset->lastClipInGroup[aw->nDrawGeometryCalls - 1] >= pset->nClips)))
	{
		return;
	}

	if (aw->clipsUpdated && aw->nDrawGeometryCalls > 0)
	{
		if (!processIntersectingPolygons(pset))
		{
			return;
		}
	}

	int lastClip;				// last clip to draw

	if (aw->clipsUpdated)
	{
		lastClip = pset->nClips - 1;
	}
	else
	{
		lastClip = pset->lastClipInGroup[aw->nDrawGeometryCalls - 1];
	}

	float forwardProgress = defaultAnimProgress(aw);

	// OpenGL stuff starts here

	if (pset->doLighting)
	{
		glPushAttrib(GL_LIGHT0);
		glPushAttrib(GL_COLOR_MATERIAL);
		glPushAttrib(GL_LIGHTING);
		glEnable(GL_COLOR_MATERIAL);
		glEnable(GL_LIGHTING);

		GLfloat ambientLight[] = { 0.3f, 0.3f, 0.3f, 0.3f };
		GLfloat diffuseLight[] = { 0.9f, 0.9f, 0.9f, 0.9f };
		GLfloat light0Position[] = { -0.5f, 0.5f, -9.0f, 0.0f };

		glLightfv(GL_LIGHT0, GL_AMBIENT, ambientLight);
		glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuseLight);
		glLightfv(GL_LIGHT0, GL_POSITION, light0Position);
	}
	glPushMatrix();

	// Store old blend values
	GLint blendSrcWas, blendDstWas;

	glGetIntegerv(GL_BLEND_SRC, &blendSrcWas);
	glGetIntegerv(GL_BLEND_DST, &blendDstWas);
	GLboolean blendWas = glIsEnabled(GL_BLEND);

	glPushAttrib(GL_STENCIL_BUFFER_BIT);
	glDisable(GL_STENCIL_TEST);

	//GLboolean normalArrayWas = glIsEnabled(GL_NORMAL_ARRAY);
	//glShadeModel(GL_FLAT);

	if (pset->doDepthTest)
	{
		// Depth test
		glPushAttrib(GL_DEPTH_FUNC);
		glPushAttrib(GL_DEPTH_TEST);
		glDepthFunc(GL_LEQUAL);
		glEnable(GL_DEPTH_TEST);
	}

	// Clip planes
	GLdouble clipPlane0[] = { 1, 0, 0, 0 };
	GLdouble clipPlane1[] = { 0, 1, 0, 0 };
	GLdouble clipPlane2[] = { -1, 0, 0, 0 };
	GLdouble clipPlane3[] = { 0, -1, 0, 0 };

	// Save old color values
	GLfloat oldColor[4];

	glGetFloatv(GL_CURRENT_COLOR, oldColor);


	// Determine where we are called from in paint.c's drawWindowTexture
	// to find out how we should change the opacity
	GLint prevActiveTexture = GL_TEXTURE0_ARB;
	Bool saturationFull = TRUE;

	if (w->screen->canDoSaturated && aw->curPaintAttrib.saturation != COLOR)
	{
		saturationFull = FALSE;
		if (w->screen->canDoSlightlySaturated &&
			aw->curPaintAttrib.saturation > 0)
		{
			if (aw->curPaintAttrib.opacity < OPAQUE ||
				aw->curPaintAttrib.brightness != BRIGHT)
				prevActiveTexture = GL_TEXTURE3_ARB;
			else
				prevActiveTexture = GL_TEXTURE2_ARB;
		}
		else
			prevActiveTexture = GL_TEXTURE1_ARB;
	}

	float brightness = aw->curPaintAttrib.brightness / 65535.0;
	float opacity = aw->curPaintAttrib.opacity / 65535.0;

	float newOpacity = opacity;
	float fadePassedBy;

	if (!blendWas)				// if translucency is not already turned on in paint.c
	{
		glEnable(GL_BLEND);
	}
	if (saturationFull)
	{
		screenTexEnvMode(w->screen, GL_MODULATE);
	}
	else if (prevActiveTexture == GL_TEXTURE2_ARB)
	{
		w->screen->activeTexture(prevActiveTexture + 1);
		enableTexture(w->screen, aw->curTexture, aw->curTextureFilter);
	}
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

	// if fade-out duration is not specified per polygon
	if (pset->allFadeDuration > -1.0f)
	{
		fadePassedBy = forwardProgress - (1 - pset->allFadeDuration);

		// if "fade out starting point" is passed
		if (fadePassedBy > 1e-5)	// if true, allFadeDuration should be > 0
		{
			float opacityFac;

			if (aw->deceleratingMotion)
				opacityFac = 1 - decelerateProgress2
						(fadePassedBy / pset->allFadeDuration);
			else
				opacityFac = 1 - fadePassedBy / pset->allFadeDuration;
			if (opacityFac < 0)
				opacityFac = 0;
			if (opacityFac > 1)
				opacityFac = 1;
			newOpacity = opacity * opacityFac;
		}
	}

	int pass;
	// 0: draw opaque ones
	// 2: draw transparent ones
	for (pass = 0; pass < 2; pass++)
	{
		int j;

		for (j = pset->firstNondrawnClip; j <= lastClip; j++)
		{
			Clip4Polygons *c = pset->clips + j;
			int nFrontVerticesTilThisPoly = 0;
			int nNewSides = 0;
			int i;

			for (i = 0; i < c->nIntersectingPolygons;
				 i++, nFrontVerticesTilThisPoly += nNewSides)
			{
				PolygonObject *p =
						pset->polygons + c->intersectingPolygons[i];
				nNewSides = p->nSides;

				float newOpacityPolygon = newOpacity;

				// if fade-out duration is specified per polygon
				if (pset->allFadeDuration == -1.0f)
				{
					fadePassedBy = forwardProgress - p->fadeStartTime;
					// if "fade out starting point" is passed
					if (fadePassedBy > 1e-5)	// if true, then allFadeDuration > 0
					{
						float opacityFac;

						if (aw->deceleratingMotion)
							opacityFac = 1 - decelerateProgress2
									(fadePassedBy / p->fadeDuration);
						else
							opacityFac = 1 - fadePassedBy / p->fadeDuration;
						if (opacityFac < 0)
							opacityFac = 0;
						if (opacityFac > 1)
							opacityFac = 1;
						newOpacityPolygon = newOpacity * opacityFac;
					}
				}

				if (newOpacityPolygon < 1e-5)	// if polygon object is invisible
					continue;

				if (pass == 0)
				{
					if (newOpacityPolygon < 0.9999)	// if not fully opaque
						continue;	// draw only opaque ones in pass 0
				}
				else if (newOpacityPolygon > 0.9999)	// if fully opaque
					continue;	// draw only non-opaque ones in pass 1

				glPushMatrix();

				if (pset->correctPerspective)
				{
					// Correct perspective appearance by skewing

					GLfloat skewx = -((p->centerPos.x - s->width / 2) * 1.15);
					GLfloat skewy = -((p->centerPos.y - s->height / 2) * 1.15);

					// column-major order
					GLfloat skewMat[16] =
							{1,0,0,0,
							 0,1,0,0,
							 skewx,skewy,1,0,
							 0,0,0,1};
					glMultMatrixf( skewMat);
				}

				// Center
				glTranslatef(p->centerPos.x, p->centerPos.y, p->centerPos.z);

				// Scale z first
				glScalef(1.0f, 1.0f, 1.0f / s->width);

				// Move by "rotation axis offset"
				glTranslatef(p->rotAxisOffset.x, p->rotAxisOffset.y,
							 p->rotAxisOffset.z);

				// Rotate by desired angle
				glRotatef(p->rotAngle, p->rotAxis.x, p->rotAxis.y,
						  p->rotAxis.z);

				// Move back to center
				glTranslatef(-p->rotAxisOffset.x, -p->rotAxisOffset.y,
							 -p->rotAxisOffset.z);

				// Scale back
				glScalef(1.0f, 1.0f, s->width);


				clipPlane0[3] = -(c->boxf.x1 - p->centerPosStart.x);
				clipPlane1[3] = -(c->boxf.y1 - p->centerPosStart.y);
				clipPlane2[3] = (c->boxf.x2 - p->centerPosStart.x);
				clipPlane3[3] = (c->boxf.y2 - p->centerPosStart.y);
				glClipPlane(GL_CLIP_PLANE0, clipPlane0);
				glClipPlane(GL_CLIP_PLANE1, clipPlane1);
				glClipPlane(GL_CLIP_PLANE2, clipPlane2);
				glClipPlane(GL_CLIP_PLANE3, clipPlane3);

				int k;

				for (k = 0; k < 4; k++)
					glEnable(GL_CLIP_PLANE0 + k);
				Bool fadeBackAndSides =
						pset->backAndSidesFadeDur > 0 &&
						forwardProgress <= pset->backAndSidesFadeDur;

				float newOpacityPolygon2 = newOpacityPolygon;

				if (fadeBackAndSides)
				{
					// Fade-in opacity for back face and sides
					newOpacityPolygon2 *=
							(forwardProgress / pset->backAndSidesFadeDur);
				}

				if (saturationFull)
					glColor4f(brightness, brightness, brightness,
							  newOpacityPolygon2);
				else if (prevActiveTexture == GL_TEXTURE1_ARB)
				{
					// From paint.c

					GLfloat constant2[4] =
							{ 0.5f +
						0.5f * RED_SATURATION_WEIGHT * brightness,
						0.5f + 0.5f * GREEN_SATURATION_WEIGHT * brightness,
						0.5f + 0.5f * BLUE_SATURATION_WEIGHT * brightness,
						newOpacityPolygon2
					};

					glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR,
							   constant2);
				}
				else			//if (prevActiveTexture >= GL_TEXTURE2_ARB)
				{
					GLfloat constant2[4] = { brightness,
						brightness,
						brightness,
						newOpacityPolygon2
					};

					// From paint.c

					glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR,
							   constant2);

					glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE,
							  GL_COMBINE);

					glTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_MODULATE);
					glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE0_RGB, GL_PREVIOUS);
					glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE1_RGB, GL_CONSTANT);
					glTexEnvf(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR);
					glTexEnvf(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR);

					glTexEnvf(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_MODULATE);
					glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA, GL_PREVIOUS);
					glTexEnvf(GL_TEXTURE_ENV, GL_SOURCE1_ALPHA, GL_CONSTANT);
					glTexEnvf(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA,
							  GL_SRC_ALPHA);
					glTexEnvf(GL_TEXTURE_ENV, GL_OPERAND1_ALPHA,
							  GL_SRC_ALPHA);
				}

				//glEnableClientState(GL_NORMAL_ARRAY);
				//glEnable(GL_NORMALIZE);

				// Draw back face
				glVertexPointer(3, GL_FLOAT, 0, p->vertices + 3 * p->nSides);
				//glNormalPointer(GL_FLOAT, 0,
				//              p->normals + 3 * p->nSides);
				glTexCoordPointer(2, GL_FLOAT, 0,
								  c->polygonVertexTexCoords +
								  2 * (2 * nFrontVerticesTilThisPoly +
									   p->nSides));

				glNormal3f(p->normals[3], p->normals[4], p->normals[5]);
				glDrawArrays(GL_POLYGON, 0, p->nSides);

				// Front vertex coords
				glVertexPointer(3, GL_FLOAT, 0, p->vertices);
				//glNormalPointer(GL_FLOAT, 0, p->normals);
				glTexCoordPointer(2, GL_FLOAT, 0,
								  c->polygonVertexTexCoords +
								  2 * 2 * nFrontVerticesTilThisPoly);

				//if (pset->thickness > 1e-5)
				{
					// TODO: Surface normals for sides
					// Draw quad strip for sides
					if (TRUE)
					{
						// Do each quad separately to be able to specify
						// different normals
						for (k = 0; k < p->nSides; k++)
						{
							/*
							   int k2 = (k + 2) * 3;
							   glNormal3f(p->normals[k2 + 0],
							   p->normals[k2 + 1],
							   p->normals[k2 + 2]); */
							glNormal3f(p->normals[0],	// front face normal for now
									   p->normals[1], p->normals[2]);
							glDrawElements(GL_QUADS, 4,
										   GL_UNSIGNED_SHORT,
										   p->sideIndices + k * 4);
						}
					}
					else			// no need for separate quad rendering
						glDrawElements(GL_QUAD_STRIP, 2 * (p->nSides + 1),
									   GL_UNSIGNED_SHORT, p->sideIndices);
				}
				if (fadeBackAndSides)
					// if opacity was changed just above
				{
					// Go back to normal opacity for front face

					if (saturationFull)
						glColor4f(brightness, brightness, brightness,
								  newOpacityPolygon);
					else if (prevActiveTexture == GL_TEXTURE1_ARB)
					{
						GLfloat constant[4] =
								{ 0.5f +
							0.5f * RED_SATURATION_WEIGHT * brightness,
							0.5f + 0.5f * GREEN_SATURATION_WEIGHT *
									brightness,
							0.5f + 0.5f * BLUE_SATURATION_WEIGHT * brightness,
							newOpacityPolygon
						};

						glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR,
								   constant);
					}
					else
					{
						GLfloat constant[4] = { brightness,
							brightness,
							brightness,
							newOpacityPolygon
						};
						glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR,
								   constant);
					}
				}
				// Draw front face

				glNormal3f(p->normals[0], p->normals[1], p->normals[2]);
				glDrawArrays(GL_POLYGON, 0, p->nSides);

				for (k = 0; k < 4; k++)
					glDisable(GL_CLIP_PLANE0 + k);

				glPopMatrix();
			}
		}
	}
	// Restore
	// -----------------------------------------

	// Restore old color values
	glColor4f(oldColor[0], oldColor[1], oldColor[2], oldColor[3]);
	//screenTexEnvMode(w->screen, GL_REPLACE);

	glPopAttrib();
	if (pset->doDepthTest)
	{
		glPopAttrib();
		glPopAttrib();
	}

	//if (!normalArrayWas)
	//  glDisableClientState(GL_NORMAL_ARRAY);

	// Restore texture stuff
	if (saturationFull)
		screenTexEnvMode(w->screen, GL_REPLACE);
	else if (prevActiveTexture == GL_TEXTURE2_ARB)
	{
		disableTexture(w->screen, aw->curTexture);
		glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
		w->screen->activeTexture(prevActiveTexture);
	}
	// Restore blend values
	if (!blendWas)
		glDisable(GL_BLEND);
	glBlendFunc(blendSrcWas, blendDstWas);

	glPopMatrix();

	if (pset->doLighting)		// && !s->lighting)
	{
		glPopAttrib();
		glPopAttrib();
		glPopAttrib();
	}
	if (aw->clipsUpdated)		// set end mark for this group of clips
		pset->lastClipInGroup[aw->nDrawGeometryCalls - 1] = lastClip;

	// Next time, start drawing from next group of clips
	pset->firstNondrawnClip =
			pset->lastClipInGroup[aw->nDrawGeometryCalls - 1] + 1;
}

static void polygonsPrePaintWindow(CompScreen * s, CompWindow * w)
{
	ANIM_WINDOW(w);
	if (aw->polygonSet)
		aw->polygonSet->firstNondrawnClip = 0;
}

static void polygonsPostPaintWindow(CompScreen * s, CompWindow * w)
{
	ANIM_WINDOW(w);
	if (aw->clipsUpdated &&	// clips should be dropped only in the 1st step
		aw->polygonSet && aw->nDrawGeometryCalls == 0)	// if clips not drawn
	{
		// drop these unneeded clips (e.g. ones passed by blurfx)
		aw->polygonSet->nClips = aw->polygonSet->firstNondrawnClip;
	}
}

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

	if (!tessellateIntoRectangles(w, 20, 14, 15.0f))
		return;

	PolygonSet *pset = aw->polygonSet;
	PolygonObject *p = pset->polygons;
	float sqrt2 = sqrt(2);

	int i;

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

		float screenSizeFactor = (0.8 * DEFAULT_Z_CAMERA * s->width);
		float speed = screenSizeFactor / 10 * (0.2 + RAND_FLOAT());

		float xx = 2 * (p->centerRelPos.x - 0.5);
		float yy = 2 * (p->centerRelPos.y - 0.5);

		float x = speed * (xx + 0.5 * (RAND_FLOAT() - 0.5));
		float y = speed * (yy + 0.5 * (RAND_FLOAT() - 0.5));

		float zbias = 0.3;
		float z =
				speed * 10 * (zbias +
							  (sqrt2 - sqrt(xx * xx + yy * yy)) / sqrt2);

		p->finalRelPos.x = x;
		p->finalRelPos.y = y;
		p->finalRelPos.z = z;
		p->finalRotAng = 270;
	}
	pset->allFadeDuration = 0.2f;
	pset->doDepthTest = TRUE;
	pset->doLighting = TRUE;
	pset->correctPerspective = TRUE;
	pset->backAndSidesFadeDur = 0.2f;
}

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

	if (!tessellateIntoRectangles(w, 20, 14, 15.0f))
		return;

	PolygonSet *pset = aw->polygonSet;
	PolygonObject *p = pset->polygons;
	float fadeDuration = 0.26;
	float life = 0.4;
	float spreadFac = 3.5;
	float randYMax = 0.07;
	float winFacX = WIN_W(w) / 800.0;
	float winFacY = WIN_H(w) / 800.0;
	float winFacZ = (WIN_H(w) + WIN_W(w)) / 2.0 / 800.0;

	int i;

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

		float screenSizeFactor = (0.8 * DEFAULT_Z_CAMERA * s->width);
		float speed = screenSizeFactor / 10 * (0.2 + RAND_FLOAT());

		float xx = 2 * (p->centerRelPos.x - 0.5);
		float yy = 2 * (p->centerRelPos.y - 0.5);

		float x =
				speed * winFacX * spreadFac * (xx +
											   0.5 * (RAND_FLOAT() - 0.5));
		float y =
				speed * winFacY * spreadFac * (yy +
											   0.5 * (RAND_FLOAT() - 0.5));
		float z = speed * winFacZ * 7 * ((RAND_FLOAT() - 0.5) / 0.5);

		p->finalRelPos.x = x;
		p->finalRelPos.y = y;
		p->finalRelPos.z = z;

		p->moveStartTime =
				p->centerRelPos.y * (1 - fadeDuration - randYMax) +
				randYMax * RAND_FLOAT();
		p->moveDuration = 1;

		p->fadeStartTime = p->moveStartTime + life;
		if (p->fadeStartTime > 1 - fadeDuration)
			p->fadeStartTime = 1 - fadeDuration;
		p->fadeDuration = fadeDuration;

		p->finalRotAng = 150;
	}
	pset->doDepthTest = TRUE;
	pset->doLighting = TRUE;
	pset->correctPerspective = TRUE;
}

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

	// Sub effects:
	// 1: Domino
	// 2: Razr
	int subEffectNo = aw->subEffectNo;

	int fallDir;

	if (subEffectNo == 2)
		fallDir = getAnimationDirection
				(w, as->opt[ANIM_SCREEN_OPTION_RAZR_DIRECTION].value.s, TRUE);
	else
		fallDir = getAnimationDirection
				(w, as->opt[ANIM_SCREEN_OPTION_DOMINO_DIRECTION].value.s,
				 TRUE);

	int defaultGridSize = 20;
	float minCellSize = 30;
	int gridSizeX;
	int gridSizeY;
	int fallDirGridSize;
	float minDistStartEdge;		// half piece size in [0,1] range
	float gridCellW;
	float gridCellH;
	float cellAspectRatio = 1.25;

	if (subEffectNo == 2)
		cellAspectRatio = 1;

	// Determine sensible domino piece sizes
	if (fallDir == AnimDirectionDown || fallDir == AnimDirectionUp)
	{
		if (minCellSize > BORDER_W(w))
			minCellSize = BORDER_W(w);
		if (BORDER_W(w) / defaultGridSize < minCellSize)
			gridSizeX = (int)(BORDER_W(w) / minCellSize);
		else
			gridSizeX = defaultGridSize;
		gridCellW = BORDER_W(w) / gridSizeX;
		gridSizeY = (int)(BORDER_H(w) / (gridCellW * cellAspectRatio));
		if (gridSizeY == 0)
			gridSizeY = 1;
		gridCellH = BORDER_H(w) / gridSizeY;
		fallDirGridSize = gridSizeY;
	}
	else
	{
		if (minCellSize > BORDER_H(w))
			minCellSize = BORDER_H(w);
		if (BORDER_H(w) / defaultGridSize < minCellSize)
			gridSizeY = (int)(BORDER_H(w) / minCellSize);
		else
			gridSizeY = defaultGridSize;
		gridCellH = BORDER_H(w) / gridSizeY;
		gridSizeX = (int)(BORDER_W(w) / (gridCellH * cellAspectRatio));
		if (gridSizeX == 0)
			gridSizeX = 1;
		gridCellW = BORDER_W(w) / gridSizeX;
		fallDirGridSize = gridSizeX;
	}
	minDistStartEdge = (1.0 / fallDirGridSize) / 2;

	float thickness = MIN(gridCellW, gridCellH) / 3.5;

	if (!tessellateIntoRectangles(w, gridSizeX, gridSizeY, thickness))
		return;

	float rotAxisX = 0;
	float rotAxisY = 0;
	Point3d rotAxisOff = { 0, 0, thickness / 2 };
	float posX = 0;				// position of standing piece
	float posY = 0;
	float posZ = 0;
	int nFallingColumns = gridSizeX;
	float gridCellHalfW = gridCellW / 2;
	float gridCellHalfH = gridCellH / 2;

	switch (fallDir)
	{
	case AnimDirectionDown:
		rotAxisX = -1;
		if (subEffectNo == 2)
			rotAxisOff.y = -gridCellHalfH;
		else
		{
			posY = -(gridCellHalfH + thickness);
			posZ = gridCellHalfH - thickness / 2;
		}
		break;
	case AnimDirectionLeft:
		rotAxisY = -1;
		if (subEffectNo == 2)
			rotAxisOff.x = gridCellHalfW;
		else
		{
			posX = gridCellHalfW + thickness;
			posZ = gridCellHalfW - thickness / 2;
		}
		nFallingColumns = gridSizeY;
		break;
	case AnimDirectionUp:
		rotAxisX = 1;
		if (subEffectNo == 2)
			rotAxisOff.y = gridCellHalfH;
		else
		{
			posY = gridCellHalfH + thickness;
			posZ = gridCellHalfH - thickness / 2;
		}
		break;
	case AnimDirectionRight:
		rotAxisY = 1;
		if (subEffectNo == 2)
			rotAxisOff.x = -gridCellHalfW;
		else
		{
			posX = -(gridCellHalfW + thickness);
			posZ = gridCellHalfW - thickness / 2;
		}
		nFallingColumns = gridSizeY;
		break;
	}

	float fadeDuration;
	float riseDuration;
	float riseTimeRandMax = 0.2;

	if (subEffectNo == 2)
	{
		riseDuration = (1 - riseTimeRandMax) / fallDirGridSize;
		fadeDuration = riseDuration / 2;
	}
	else
	{
		fadeDuration = 0.18;
		riseDuration = 0.2;
	}
	float *riseTimeRandSeed = calloc(1, nFallingColumns * sizeof(float));

	if (!riseTimeRandSeed)
		// TODO: log error here
		return;
	int i;

	for (i = 0; i < nFallingColumns; i++)
		riseTimeRandSeed[i] = RAND_FLOAT();

	PolygonSet *pset = aw->polygonSet;
	PolygonObject *p = pset->polygons;

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

		p->finalRelPos.x = posX;
		p->finalRelPos.y = posY;
		p->finalRelPos.z = posZ;

		// dist. from rise-start / fall-end edge in window ([0,1] range)
		float distStartEdge = 0;

		// dist. from edge perpendicular to movement (for move start time)
		// so that same the blocks in same row/col. appear to knock down
		// the next one
		float distPerpEdge = 0;

		switch (fallDir)
		{
		case AnimDirectionUp:
			distStartEdge = p->centerRelPos.y;
			distPerpEdge = p->centerRelPos.x;
			break;
		case AnimDirectionRight:
			distStartEdge = 1 - p->centerRelPos.x;
			distPerpEdge = p->centerRelPos.y;
			break;
		case AnimDirectionDown:
			distStartEdge = 1 - p->centerRelPos.y;
			distPerpEdge = p->centerRelPos.x;
			break;
		case AnimDirectionLeft:
			distStartEdge = p->centerRelPos.x;
			distPerpEdge = p->centerRelPos.y;
			break;
		}

		float riseTimeRand =
				riseTimeRandSeed[(int)(distPerpEdge * nFallingColumns)] *
				riseTimeRandMax;

		p->moveDuration = riseDuration;

		float mult = 1;
		if (fallDirGridSize > 1)
			mult = ((distStartEdge - minDistStartEdge) /
					(1 - 2 * minDistStartEdge));
		if (subEffectNo == 2)
		{
			p->moveStartTime =
					mult *
					(1 - riseDuration - riseTimeRandMax) + riseTimeRand;
			p->fadeStartTime = p->moveStartTime + riseDuration / 2;
			p->finalRotAng = -180;

			p->rotAxisOffset.x = rotAxisOff.x;
			p->rotAxisOffset.y = rotAxisOff.y;
			p->rotAxisOffset.z = rotAxisOff.z;
		}
		else
		{
			p->moveStartTime =
					mult *
					(1 - riseDuration - riseTimeRandMax) +
					riseTimeRand;
			p->fadeStartTime =
					p->moveStartTime + riseDuration - riseTimeRand + 0.03;
			p->finalRotAng = -90;
		}
		if (p->fadeStartTime > 1 - fadeDuration)
			p->fadeStartTime = 1 - fadeDuration;
		p->fadeDuration = fadeDuration;
	}
	free(riseTimeRandSeed);
	pset->doDepthTest = TRUE;
	pset->doLighting = TRUE;
	pset->correctPerspective = TRUE;
}

/*
static void fxTornado3DInit(CompScreen * s, CompWindow * w)
{
	//ANIM_WINDOW(w);

	if (!tessellateIntoRectangles(w, 20, 14, 15.0f))
		return;
}
*/

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

	float finalDistFac;
	float finalRotAng;
	float thickness;

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

	if (thickness < 1e-5) // if thicknes is 0, we'll make the effect 2D
	{
		// store window opacity
		aw->storedOpacity = w->paint.opacity;
		aw->timestep = (s->slowAnimations ? 2 :	// For smooth slow-mo
						as->opt[ANIM_SCREEN_OPTION_TIME_STEP].value.i);

		return; // we're done with 2D initialization
	}

	// for 3D glide effect
	// ------------------------

	PolygonSet *pset = aw->polygonSet;

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

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

	PolygonObject *p = pset->polygons;

	int i;

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

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

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

// Computes polygon's new position and orientation
// with linear movement
static void
polygonsLinearAnimStepPolygon(CompWindow * w,
							  PolygonObject * p, float forwardProgress)
{
	float moveProgress = forwardProgress - p->moveStartTime;

	if (p->moveDuration > 0)
		moveProgress /= p->moveDuration;
	if (moveProgress < 0)
		moveProgress = 0;
	else if (moveProgress > 1)
		moveProgress = 1;

	p->centerPos.x = moveProgress * p->finalRelPos.x + p->centerPosStart.x;
	p->centerPos.y = moveProgress * p->finalRelPos.y + p->centerPosStart.y;
	p->centerPos.z = 1.0f / w->screen->width *
			moveProgress * p->finalRelPos.z + p->centerPosStart.z;

	p->rotAngle = moveProgress * p->finalRotAng + p->rotAngleStart;
}

// Similar to polygonsLinearAnimStepPolygon,
// but slightly ac/decelerates movement
static void
polygonsDeceleratingAnimStepPolygon(CompWindow * w,
									PolygonObject * p, float forwardProgress)
{
	float moveProgress = forwardProgress - p->moveStartTime;

	if (p->moveDuration > 0)
		moveProgress /= p->moveDuration;
	if (moveProgress < 0)
		moveProgress = 0;
	else if (moveProgress > 1)
		moveProgress = 1;

	moveProgress = decelerateProgress2(moveProgress);

	p->centerPos.x = moveProgress * p->finalRelPos.x + p->centerPosStart.x;
	p->centerPos.y = moveProgress * p->finalRelPos.y + p->centerPosStart.y;
	p->centerPos.z = 1.0f / w->screen->width *
			moveProgress * p->finalRelPos.z + p->centerPosStart.z;

	p->rotAngle = moveProgress * p->finalRotAng + p->rotAngleStart;
}

// =====================  End of Effects  =========================


AnimEffectProperties animEffectProperties[AnimEffectNum] = {
	// AnimEffectNone
	{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
	,
	// AnimEffectRandom
	{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
	,
	// AnimEffectBeamUp
	{fxBeamupUpdateWindowAttrib, 0, drawParticleSystems, fxBeamUpModelStep,
	 fxBeamUpInit, 0, 0, 0, 1, 0, 0}
	,
	// AnimEffectBurn
	{0, 0, drawParticleSystems, fxBurnModelStep, fxBurnInit, 0, 0, 0, 1, 0, 0}
	,
	// AnimEffectCurvedFold
	{0, 0, 0, fxCurvedFoldModelStep, 0, fxMagicLamp1InitGrid, 0, 0, 0, 0, 0}
	,
	// AnimEffectDomino3D
	{0, polygonsPrePaintWindow, polygonsPostPaintWindow, polygonsAnimStep,
	 fxDomino3DInit, 0, polygonsStoreClips, polygonsDrawCustomGeometry, 0,
	 polygonsLinearAnimStepPolygon, 1}
	,
	// AnimEffectDream
	{fxDreamUpdateWindowAttrib, 0, 0, fxDreamModelStep, fxDreamInit,
	 fxMagicLamp1InitGrid, 0, 0, 0, 0, 0}
	,
	// AnimEffectExplode3D
	{0, polygonsPrePaintWindow, polygonsPostPaintWindow, polygonsAnimStep,
	 fxExplode3DInit, 0, polygonsStoreClips, polygonsDrawCustomGeometry, 0,
	 polygonsLinearAnimStepPolygon, 0}
	,
	// AnimEffectFade
	{fxFadeUpdateWindowAttrib, 0, 0, fxFadeModelStep, fxFadeInit, 0, 0, 0, 0,
	 0, 0}
	,
	// AnimEffectGlide3D1
	{fxGlideUpdateWindowAttrib, polygonsPrePaintWindow,
	 polygonsPostPaintWindow, fxGlideAnimStep,
	 fxGlideInit, 0, polygonsStoreClips, polygonsDrawCustomGeometry, 0,
	 polygonsDeceleratingAnimStepPolygon, 1}
	,
	// AnimEffectGlide3D2
	{fxGlideUpdateWindowAttrib, polygonsPrePaintWindow,
	 polygonsPostPaintWindow, fxGlideAnimStep,
	 fxGlideInit, 0, polygonsStoreClips, polygonsDrawCustomGeometry, 0,
	 polygonsDeceleratingAnimStepPolygon, 2}
	,
	// AnimEffectHorizontalFolds
	{0, 0, 0, fxHorizontalFoldsModelStep, 0, fxHorizontalFoldsInitGrid,
	 0, 0, 0, 0, 0}
	,
	// AnimEffectLeafSpread3D
	{0, polygonsPrePaintWindow, polygonsPostPaintWindow, polygonsAnimStep,
	 fxLeafSpread3DInit, 0, polygonsStoreClips, polygonsDrawCustomGeometry, 0,
	 polygonsLinearAnimStepPolygon, 0}
	,
	// AnimEffectMagicLamp1
	{0, 0, 0, fxMagicLampModelStep, fxMagicLampInit, fxMagicLamp1InitGrid,
	 0, 0, 0, 0, 0}
	,
	// AnimEffectMagicLamp2
	{0, 0, 0, fxMagicLampModelStep, fxMagicLampInit, fxMagicLamp2InitGrid,
	 0, 0, 0, 0, 0}
	,
	// AnimEffectMagicLamp3
	{0, 0, 0, fxMagicLampModelStep, fxMagicLampInit,
	 fxMagicLamp3InitGrid, 0}
	,
	// AnimEffectRazr3D
	{0, polygonsPrePaintWindow, polygonsPostPaintWindow, polygonsAnimStep,
	 fxDomino3DInit, 0, polygonsStoreClips, polygonsDrawCustomGeometry, 0,
	 polygonsLinearAnimStepPolygon, 2}
	,
	// AnimEffectRollUp
	{0, 0, 0, fxRollUpModelStep, 0, fxRollUpInitGrid, 0, 0, 1, 0, 0}
	,
	// AnimEffectSidekick
	{fxZoomUpdateWindowAttrib, 0, 0, fxZoomModelStep, fxSidekickInit,
	 0, 0, 0, 1, 0, 0}
	,
	// AnimEffectWave
	{0, 0, 0, fxWaveModelStep, 0, fxMagicLamp1InitGrid, 0, 0, 0, 0, 0}
	,
	// AnimEffectZoom
	{fxZoomUpdateWindowAttrib, 0, 0, fxZoomModelStep, fxZoomInit, 0, 0, 0, 1,
	 0, 0}
};


static 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
animSetScreenOption(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_SIDEKICK_NUM_ROTATIONS:
		if (compSetFloatOption(o, value))
		{
			return TRUE;
		}
		break;
	case ANIM_SCREEN_OPTION_ZOOM_FROM_CENTER:
		if (compSetStringOption(o, value))
		{
			int i;

			for (i = 0; i < 4; i++)
			{
				if (strcmp(o->value.s, zoomFromCenterOption[i]) == 0)
				{
					as->zoomFC = i;
					return TRUE;
				}
			}
		}
		break;
	case ANIM_SCREEN_OPTION_TIME_STEP:
		if (compSetIntOption(o, value))
		{
			return TRUE;
		}
		break;
	case ANIM_SCREEN_OPTION_TIME_STEP_INTENSE:
		if (compSetIntOption(o, value))
		{
			return TRUE;
		}
		break;
	case ANIM_SCREEN_OPTION_DISABLE_PP_FX:
		if (compSetStringOption(o, value))
		{
			int i;

			for (i = 0; i < 3; i++)
			{
				if (strcmp(o->value.s, ppDisablingName[i]) == 0)
				{
					as->ppDisabling = i;
					return TRUE;
				}
			}
		}
		break;
	case ANIM_SCREEN_OPTION_MAGIC_LAMP1_GRID_RES:
		if (compSetIntOption(o, value))
		{
			return TRUE;
		}
		break;
	case ANIM_SCREEN_OPTION_MAGIC_LAMP1_MAX_WAVES:
		if (compSetIntOption(o, value))
		{
			return TRUE;
		}
		break;
	case ANIM_SCREEN_OPTION_MAGIC_LAMP1_WAVE_AMP_MIN:
		if (compSetFloatOption(o, value))
		{
			return TRUE;
		}
		break;
	case ANIM_SCREEN_OPTION_MAGIC_LAMP1_WAVE_AMP_MAX:
		if (compSetFloatOption(o, value))
		{
			return TRUE;
		}
		break;
	case ANIM_SCREEN_OPTION_MAGIC_LAMP1_CREATE_START_WIDTH:
		if (compSetIntOption(o, value))
		{
			return TRUE;
		}
		break;
	case ANIM_SCREEN_OPTION_MAGIC_LAMP2_GRID_RES:
		if (compSetIntOption(o, value))
		{
			return TRUE;
		}
		break;
	case ANIM_SCREEN_OPTION_MAGIC_LAMP2_MAX_WAVES:
		if (compSetIntOption(o, value))
		{
			return TRUE;
		}
		break;
	case ANIM_SCREEN_OPTION_MAGIC_LAMP2_WAVE_AMP_MIN:
		if (compSetFloatOption(o, value))
		{
			return TRUE;
		}
		break;
	case ANIM_SCREEN_OPTION_MAGIC_LAMP2_WAVE_AMP_MAX:
		if (compSetFloatOption(o, value))
		{
			return TRUE;
		}
		break;
	case ANIM_SCREEN_OPTION_MAGIC_LAMP2_CREATE_START_WIDTH:
		if (compSetIntOption(o, value))
		{
			return TRUE;
		}
		break;
	case ANIM_SCREEN_OPTION_MAGIC_LAMP3_GRID_RES:
		if (compSetIntOption(o, value))
		{
			return TRUE;
		}
		break;
	case ANIM_SCREEN_OPTION_MAGIC_LAMP3_MAX_WAVES:
		if (compSetIntOption(o, value))
		{
			return TRUE;
		}
		break;
	case ANIM_SCREEN_OPTION_MAGIC_LAMP3_WAVE_AMP_MIN:
		if (compSetFloatOption(o, value))
		{
			return TRUE;
		}
		break;
	case ANIM_SCREEN_OPTION_MAGIC_LAMP3_WAVE_AMP_MAX:
		if (compSetFloatOption(o, value))
		{
			return TRUE;
		}
		break;
	case ANIM_SCREEN_OPTION_MAGIC_LAMP3_CREATE_START_WIDTH:
		if (compSetIntOption(o, value))
		{
			return TRUE;
		}
		break;
	case ANIM_SCREEN_OPTION_FOCUS_WAVE_WIDTH:
		if (compSetFloatOption(o, value))
		{
			return TRUE;
		}
		break;
	case ANIM_SCREEN_OPTION_FOCUS_WAVE_AMP:
		if (compSetFloatOption(o, value))
		{
			return TRUE;
		}
		break;
	case ANIM_SCREEN_OPTION_CURVED_FOLD_AMP:
		if (compSetFloatOption(o, value))
		{
			return TRUE;
		}
		break;
	case ANIM_SCREEN_OPTION_HORIZONTAL_FOLDS_AMP:
		if (compSetFloatOption(o, value))
		{
			return TRUE;
		}
		break;
	case ANIM_SCREEN_OPTION_HORIZONTAL_FOLDS_NUM_FOLDS:
		if (compSetIntOption(o, value))
		{
			return TRUE;
		}
		break;
	case ANIM_SCREEN_OPTION_ROLLUP_FIXED_INTERIOR:
		if (compSetBoolOption(o, value))
			return TRUE;
		break;
	case ANIM_SCREEN_OPTION_ALL_RANDOM:
		if (compSetBoolOption(o, value))
			return TRUE;
		break;
	case ANIM_SCREEN_OPTION_MINIMIZE_EFFECT:
		if (compSetStringOption(o, value))
		{
			int i;

			for (i = 0; i < NUM_MINIMIZE_EFFECT; i++)
			{
				if (strcmp(o->value.s, minimizeEffectName[i]) == 0)
				{
					as->minimizeEffect = minimizeEffectType[i];
					return TRUE;
				}
			}
		}
		break;
	case ANIM_SCREEN_OPTION_UNMINIMIZE_EFFECT:
		if (compSetStringOption(o, value))
		{
			int i;

			for (i = 0; i < NUM_MINIMIZE_EFFECT; i++)
			{
				if (strcmp(o->value.s, minimizeEffectName[i]) == 0)
				{
					as->unminimizeEffect = minimizeEffectType[i];
					return TRUE;
				}
			}
		}
		break;
	case ANIM_SCREEN_OPTION_CLOSE1_EFFECT:
		if (compSetStringOption(o, value))
		{
			int i;

			for (i = 0; i < NUM_CLOSE_EFFECT; i++)
			{
				if (strcmp(o->value.s, closeEffectName[i]) == 0)
				{
					as->close1Effect = closeEffectType[i];
					return TRUE;
				}
			}
		}
		break;
	case ANIM_SCREEN_OPTION_CLOSE2_EFFECT:
		if (compSetStringOption(o, value))
		{
			int i;

			for (i = 0; i < NUM_CLOSE_EFFECT; i++)
			{
				if (strcmp(o->value.s, closeEffectName[i]) == 0)
				{
					as->close2Effect = closeEffectType[i];
					return TRUE;
				}
			}
		}
		break;
	case ANIM_SCREEN_OPTION_CREATE1_EFFECT:
		if (compSetStringOption(o, value))
		{
			int i;

			for (i = 0; i < NUM_CLOSE_EFFECT; i++)
			{
				if (strcmp(o->value.s, closeEffectName[i]) == 0)
				{
					as->create1Effect = closeEffectType[i];
					return TRUE;
				}
			}
		}
		break;
	case ANIM_SCREEN_OPTION_CREATE2_EFFECT:
		if (compSetStringOption(o, value))
		{
			int i;

			for (i = 0; i < NUM_CLOSE_EFFECT; i++)
			{
				if (strcmp(o->value.s, closeEffectName[i]) == 0)
				{
					as->create2Effect = closeEffectType[i];
					return TRUE;
				}
			}
		}
		break;
	case ANIM_SCREEN_OPTION_FOCUS_EFFECT:
		if (compSetStringOption(o, value))
		{
			int i;

			for (i = 0; i < NUM_FOCUS_EFFECT; i++)
			{
				if (strcmp(o->value.s, focusEffectName[i]) == 0)
				{
					as->focusEffect = focusEffectType[i];
					return TRUE;
				}
			}
		}
		break;
	case ANIM_SCREEN_OPTION_SHADE_EFFECT:
		if (compSetStringOption(o, value))
		{
			int i;

			for (i = 0; i < NUM_SHADE_EFFECT; i++)
			{
				if (strcmp(o->value.s, shadeEffectName[i]) == 0)
				{
					as->shadeEffect = shadeEffectType[i];
					return TRUE;
				}
			}
		}
		break;
	case ANIM_SCREEN_OPTION_UNSHADE_EFFECT:
		if (compSetStringOption(o, value))
		{
			int i;

			for (i = 0; i < NUM_SHADE_EFFECT; i++)
			{
				if (strcmp(o->value.s, shadeEffectName[i]) == 0)
				{
					as->unshadeEffect = shadeEffectType[i];
					return TRUE;
				}
			}
		}
		break;
	case ANIM_SCREEN_OPTION_MINIMIZE_WINDOW_TYPE:
		if (compSetOptionList(o, value))
		{
			as->minimizeWMask = compWindowTypeMaskFromStringList(&o->value);
			return TRUE;
		}
		break;
	case ANIM_SCREEN_OPTION_UNMINIMIZE_WINDOW_TYPE:
		if (compSetOptionList(o, value))
		{
			as->unminimizeWMask = compWindowTypeMaskFromStringList(&o->value);
			return TRUE;
		}
		break;
	case ANIM_SCREEN_OPTION_CLOSE1_WINDOW_TYPE:
		if (compSetOptionList(o, value))
		{
			as->close1WMask = compWindowTypeMaskFromStringList(&o->value);
			return TRUE;
		}
		break;
	case ANIM_SCREEN_OPTION_CLOSE2_WINDOW_TYPE:
		if (compSetOptionList(o, value))
		{
			as->close2WMask = compWindowTypeMaskFromStringList(&o->value);
			return TRUE;
		}
		break;
	case ANIM_SCREEN_OPTION_CREATE1_WINDOW_TYPE:
		if (compSetOptionList(o, value))
		{
			as->create1WMask = compWindowTypeMaskFromStringList(&o->value);
			return TRUE;
		}
		break;
	case ANIM_SCREEN_OPTION_CREATE2_WINDOW_TYPE:
		if (compSetOptionList(o, value))
		{
			as->create2WMask = compWindowTypeMaskFromStringList(&o->value);
			return TRUE;
		}
		break;
	case ANIM_SCREEN_OPTION_FOCUS_WINDOW_TYPE:
		if (compSetOptionList(o, value))
		{
			as->focusWMask = compWindowTypeMaskFromStringList(&o->value);
			return TRUE;
		}
		break;
	case ANIM_SCREEN_OPTION_SHADE_WINDOW_TYPE:
		if (compSetOptionList(o, value))
		{
			as->shadeWMask = compWindowTypeMaskFromStringList(&o->value);
			return TRUE;
		}
		break;
	case ANIM_SCREEN_OPTION_UNSHADE_WINDOW_TYPE:
		if (compSetOptionList(o, value))
		{
			as->unshadeWMask = compWindowTypeMaskFromStringList(&o->value);
			return TRUE;
		}
		break;
	case ANIM_SCREEN_OPTION_MINIMIZE_DURATION:
		if (compSetFloatOption(o, value))
		{
			return TRUE;
		}
		break;
	case ANIM_SCREEN_OPTION_UNMINIMIZE_DURATION:
		if (compSetFloatOption(o, value))
		{
			return TRUE;
		}
		break;
	case ANIM_SCREEN_OPTION_CLOSE1_DURATION:
		if (compSetFloatOption(o, value))
		{
			return TRUE;
		}
		break;
	case ANIM_SCREEN_OPTION_CLOSE2_DURATION:
		if (compSetFloatOption(o, value))
		{
			return TRUE;
		}
		break;
	case ANIM_SCREEN_OPTION_CREATE1_DURATION:
		if (compSetFloatOption(o, value))
		{
			return TRUE;
		}
		break;
	case ANIM_SCREEN_OPTION_CREATE2_DURATION:
		if (compSetFloatOption(o, value))
		{
			return TRUE;
		}
		break;
	case ANIM_SCREEN_OPTION_FOCUS_DURATION:
		if (compSetFloatOption(o, value))
		{
			return TRUE;
		}
		break;
	case ANIM_SCREEN_OPTION_SHADE_DURATION:
		if (compSetFloatOption(o, value))
		{
			return TRUE;
		}
		break;
	case ANIM_SCREEN_OPTION_UNSHADE_DURATION:
		if (compSetFloatOption(o, value))
		{
			return TRUE;
		}
		break;
	case ANIM_SCREEN_OPTION_FIRE_PARTICLES:
		if (compSetIntOption(o, value))
		{
			return TRUE;
		}
		break;
	case ANIM_SCREEN_OPTION_FIRE_SIZE:
		if (compSetFloatOption(o, value))
		{
			return TRUE;
		}
		break;
	case ANIM_SCREEN_OPTION_FIRE_SLOWDOWN:
		if (compSetFloatOption(o, value))
		{
			return TRUE;
		}
		break;
	case ANIM_SCREEN_OPTION_FIRE_LIFE:
		if (compSetFloatOption(o, value))
		{
			return TRUE;
		}
		break;
	case ANIM_SCREEN_OPTION_FIRE_COLOR:
		if (compSetColorOption(o, value))
		{
			return TRUE;
		}
	case ANIM_SCREEN_OPTION_FIRE_DIRECTION:
		if (compSetStringOption(o, value))
		{
			return TRUE;
		}
		break;
	case ANIM_SCREEN_OPTION_FIRE_CONSTANT_SPEED:
		if (compSetBoolOption(o, value))
			return TRUE;
		break;
	case ANIM_SCREEN_OPTION_FIRE_SMOKE:
		if (compSetBoolOption(o, value))
			return TRUE;
		break;
	case ANIM_SCREEN_OPTION_FIRE_MYSTICAL:
		if (compSetBoolOption(o, value))
			return TRUE;
		break;
	case ANIM_SCREEN_OPTION_BEAMUP_SIZE:
		if (compSetFloatOption(o, value))
		{
			return TRUE;
		}
		break;
	case ANIM_SCREEN_OPTION_BEAMUP_SPACING:
		if (compSetIntOption(o, value))
		{
			return TRUE;
		}
	case ANIM_SCREEN_OPTION_BEAMUP_COLOR:
		if (compSetColorOption(o, value))
		{
			return TRUE;
		}
		break;
	case ANIM_SCREEN_OPTION_BEAMUP_SLOWDOWN:
		if (compSetFloatOption(o, value))
		{
			return TRUE;
		}
		break;
	case ANIM_SCREEN_OPTION_BEAMUP_LIFE:
		if (compSetFloatOption(o, value))
		{
			return TRUE;
		}
		break;
	case ANIM_SCREEN_OPTION_DOMINO_DIRECTION:
		if (compSetStringOption(o, value))
		{
			return TRUE;
		}
		break;
	case ANIM_SCREEN_OPTION_RAZR_DIRECTION:
		if (compSetStringOption(o, value))
		{
			return TRUE;
		}
		break;
	case ANIM_SCREEN_OPTION_GLIDE1_AWAY_POS:
		if (compSetFloatOption(o, value))
		{
			return TRUE;
		}
		break;
	case ANIM_SCREEN_OPTION_GLIDE1_AWAY_ANGLE:
		if (compSetFloatOption(o, value))
		{
			return TRUE;
		}
		break;
	case ANIM_SCREEN_OPTION_GLIDE1_THICKNESS:
		if (compSetFloatOption(o, value))
		{
			return TRUE;
		}
		break;
	case ANIM_SCREEN_OPTION_GLIDE2_AWAY_POS:
		if (compSetFloatOption(o, value))
		{
			return TRUE;
		}
		break;
	case ANIM_SCREEN_OPTION_GLIDE2_AWAY_ANGLE:
		if (compSetFloatOption(o, value))
		{
			return TRUE;
		}
		break;
	case ANIM_SCREEN_OPTION_GLIDE2_THICKNESS:
		if (compSetFloatOption(o, value))
		{
			return TRUE;
		}
		break;
	case ANIM_SCREEN_OPTION_MINIMIZE_RANDOM_EFFECTS:
		if (compSetOptionList(o, value))
		{
			STORE_RANDOM_EFFECT_LIST
					(ANIM_SCREEN_OPTION_MINIMIZE_RANDOM_EFFECTS,
					 NUM_MINIMIZE_EFFECT,
					 ANIM_MINIMIZE_DEFAULT,
					 nMinimizeRandomEffects,
					 minimizeRandomEffects,
					 minimizeEffectName, minimizeEffectType);
			return TRUE;
		}
		break;
	case ANIM_SCREEN_OPTION_UNMINIMIZE_RANDOM_EFFECTS:
		if (compSetOptionList(o, value))
		{
			STORE_RANDOM_EFFECT_LIST
					(ANIM_SCREEN_OPTION_UNMINIMIZE_RANDOM_EFFECTS,
					 NUM_MINIMIZE_EFFECT,
					 ANIM_UNMINIMIZE_DEFAULT,
					 nUnminimizeRandomEffects,
					 unminimizeRandomEffects,
					 minimizeEffectName, minimizeEffectType);
			return TRUE;
		}
		break;
	case ANIM_SCREEN_OPTION_CLOSE1_RANDOM_EFFECTS:
		if (compSetOptionList(o, value))
		{
			STORE_RANDOM_EFFECT_LIST
					(ANIM_SCREEN_OPTION_CLOSE1_RANDOM_EFFECTS,
					 NUM_CLOSE_EFFECT,
					 ANIM_CLOSE1_DEFAULT,
					 nClose1RandomEffects,
					 close1RandomEffects, closeEffectName, closeEffectType);
			return TRUE;
		}
		break;
	case ANIM_SCREEN_OPTION_CLOSE2_RANDOM_EFFECTS:
		if (compSetOptionList(o, value))
		{
			STORE_RANDOM_EFFECT_LIST
					(ANIM_SCREEN_OPTION_CLOSE2_RANDOM_EFFECTS,
					 NUM_CLOSE_EFFECT,
					 ANIM_CLOSE2_DEFAULT,
					 nClose2RandomEffects,
					 close2RandomEffects, closeEffectName, closeEffectType);
			return TRUE;
		}
		break;
	case ANIM_SCREEN_OPTION_CREATE1_RANDOM_EFFECTS:
		if (compSetOptionList(o, value))
		{
			STORE_RANDOM_EFFECT_LIST
					(ANIM_SCREEN_OPTION_CREATE1_RANDOM_EFFECTS,
					 NUM_CLOSE_EFFECT,
					 ANIM_CREATE1_DEFAULT,
					 nCreate1RandomEffects,
					 create1RandomEffects, closeEffectName, closeEffectType);
			return TRUE;
		}
		break;
	case ANIM_SCREEN_OPTION_CREATE2_RANDOM_EFFECTS:
		if (compSetOptionList(o, value))
		{
			STORE_RANDOM_EFFECT_LIST
					(ANIM_SCREEN_OPTION_CREATE2_RANDOM_EFFECTS,
					 NUM_CLOSE_EFFECT,
					 ANIM_CREATE2_DEFAULT,
					 nCreate2RandomEffects,
					 create2RandomEffects, closeEffectName, closeEffectType);
			return TRUE;
		}
		break;
	case ANIM_SCREEN_OPTION_SHADE_RANDOM_EFFECTS:
		if (compSetOptionList(o, value))
		{
			STORE_RANDOM_EFFECT_LIST
					(ANIM_SCREEN_OPTION_SHADE_RANDOM_EFFECTS,
					 NUM_SHADE_EFFECT,
					 ANIM_SHADE_DEFAULT,
					 nShadeRandomEffects,
					 shadeRandomEffects, shadeEffectName, shadeEffectType);
			return TRUE;
		}
		break;
	case ANIM_SCREEN_OPTION_UNSHADE_RANDOM_EFFECTS:
		if (compSetOptionList(o, value))
		{
			STORE_RANDOM_EFFECT_LIST
					(ANIM_SCREEN_OPTION_UNSHADE_RANDOM_EFFECTS,
					 NUM_SHADE_EFFECT,
					 ANIM_UNSHADE_DEFAULT,
					 nUnshadeRandomEffects,
					 unshadeRandomEffects, shadeEffectName, shadeEffectType);
			return TRUE;
		}
		break;
	default:
		break;
	}

	return FALSE;
}

static void animScreenInitOptions(AnimScreen * as)
{
	CompOption *o;
	int i;

	o = &as->opt[ANIM_SCREEN_OPTION_ALL_RANDOM];
	o->name = "all_random";
	o->group = N_("Misc. Settings");
	o->subGroup = N_("");
	o->shortDesc = N_("Random Animations For All Events");
	o->advanced = False;
	o->longDesc =
			N_
			("All effects are chosen randomly, ignoring the selected effect. If None is selected for an event, that event won't be animated.");
	o->displayHints = "";
	o->type = CompOptionTypeBool;
	o->value.b = ANIM_ALL_RANDOM_DEFAULT;

	o = &as->opt[ANIM_SCREEN_OPTION_ROLLUP_FIXED_INTERIOR];
	o->name = "rollup_fixed_interior";
	o->group = N_("(Un)Shade");
	o->subGroup = N_("");
	o->advanced = False;
	o->shortDesc = N_("Rollup Fixed Interior");
	o->longDesc = N_("Fixed window interior during the Rollup animation.");
	o->displayHints = "";
	o->type = CompOptionTypeBool;
	o->value.b = ANIM_ROLLUP_FIXED_INTERIOR_DEFAULT;

	o = &as->opt[ANIM_SCREEN_OPTION_HORIZONTAL_FOLDS_NUM_FOLDS];
	o->name = "horizontal_folds_num_folds";
	o->group = N_("Horizontal Folds");
	o->subGroup = N_("");
	o->advanced = False;
	o->shortDesc = N_("Number of Horizontal Folds");
	o->longDesc =
			N_
			("The number of horizontal folds that occur in the Horizontal Fold animation.");
	o->displayHints = "";
	o->type = CompOptionTypeInt;
	o->value.i = ANIM_HORIZONTAL_FOLDS_NUM_FOLDS_DEFAULT;
	o->rest.i.min = ANIM_HORIZONTAL_FOLDS_NUM_FOLDS_MIN;
	o->rest.i.max = ANIM_HORIZONTAL_FOLDS_NUM_FOLDS_MAX;

	o = &as->opt[ANIM_SCREEN_OPTION_HORIZONTAL_FOLDS_AMP];
	o->name = "horizontal_folds_amp";
	o->group = N_("Horizontal Folds");
	o->subGroup = N_("");
	o->advanced = False;
	o->shortDesc = N_("Horizontal Fold Amplitude");
	o->longDesc =
			N_
			("Amplitude (size of the waves in the fold) of the Horizontal Folds relative to the window width. Negative values fold outward.");
	o->displayHints = "";
	o->type = CompOptionTypeFloat;
	o->value.f = ANIM_HORIZONTAL_FOLDS_AMP_DEFAULT;
	o->rest.f.min = ANIM_HORIZONTAL_FOLDS_AMP_MIN;
	o->rest.f.max = ANIM_HORIZONTAL_FOLDS_AMP_MAX;
	o->rest.f.precision = ANIM_HORIZONTAL_FOLDS_AMP_PRECISION;

	o = &as->opt[ANIM_SCREEN_OPTION_CURVED_FOLD_AMP];
	o->name = "curved_fold_amp";
	o->group = N_("Curved Folds");
	o->subGroup = N_("");
	o->advanced = False;
	o->shortDesc = N_("Curved Fold Amplitude");
	o->longDesc =
			N_
			("Amplitude (size of the waves in the fold) of the curved fold relative to window width. Negative values fold outward.");
	o->displayHints = "";
	o->type = CompOptionTypeFloat;
	o->value.f = ANIM_CURVED_FOLD_AMP_DEFAULT;
	o->rest.f.min = ANIM_CURVED_FOLD_AMP_MIN;
	o->rest.f.max = ANIM_CURVED_FOLD_AMP_MAX;
	o->rest.f.precision = ANIM_CURVED_FOLD_AMP_PRECISION;

	o = &as->opt[ANIM_SCREEN_OPTION_ZOOM_FROM_CENTER];
	o->name = "zoom_from_center";
	o->group = N_("Misc. Settings");
	o->subGroup = N_("");
	o->advanced = False;
	o->shortDesc = N_("Zoom from Center");
	o->longDesc =
			N_
			("Zoom from center when playing the Zoom and Sidekick animations.");
	o->displayHints = "";
	o->type = CompOptionTypeString;
	o->value.s = strdup(zoomFromCenterOption[ANIM_ZOOM_FROM_CENTER_DEFAULT]);
	o->rest.s.string = zoomFromCenterOption;
	o->rest.s.nString = 4;

	o = &as->opt[ANIM_SCREEN_OPTION_TIME_STEP];
	o->name = "time_step";
	o->group = N_("Misc. Settings");
	o->subGroup = N_("Advanced");
	o->advanced = False;
	o->shortDesc = N_("Animation Time Step");
	o->longDesc =
			N_
			("The amount of time in milliseconds between each render of the animation. The higher the number, the jerkier the movements become.");
	o->displayHints = "";
	o->type = CompOptionTypeInt;
	o->value.i = ANIM_TIME_STEP_DEFAULT;
	o->rest.i.min = ANIM_TIME_STEP_MIN;
	o->rest.i.max = ANIM_TIME_STEP_MAX;

	o = &as->opt[ANIM_SCREEN_OPTION_TIME_STEP_INTENSE];
	o->name = "time_step_intense";
	o->group = N_("Misc. Settings");
	o->subGroup = N_("Advanced");
	o->advanced = False;
	o->shortDesc = N_("Animation Time Step For Intense Effects");
	o->longDesc =
			N_
			("The amount of time in milliseconds between each render of the intense animation (Ex. Burn, Beam). The higher the number, the jerkier the movements become.");
	o->displayHints = "";
	o->type = CompOptionTypeInt;
	o->value.i = ANIM_TIME_STEP_INTENSE_DEFAULT;
	o->rest.i.min = ANIM_TIME_STEP_INTENSE_MIN;
	o->rest.i.max = ANIM_TIME_STEP_INTENSE_MAX;

	o = &as->opt[ANIM_SCREEN_OPTION_DISABLE_PP_FX];
	o->name = "disable_pp_fx";
	o->group = N_("Misc. Settings");
	o->subGroup = N_("Advanced");
	o->advanced = False;
	o->shortDesc = N_("Disable Post-processing Effects During Animation");
	o->longDesc = N_("Disables Blur effects during animations.");
	o->displayHints = "";
	o->type = CompOptionTypeString;
	o->value.s = strdup(ppDisablingName[ANIM_DISABLE_PP_FX_DEFAULT]);
	o->rest.s.string = ppDisablingName;
	o->rest.s.nString = 3;

	o = &as->opt[ANIM_SCREEN_OPTION_MAGIC_LAMP1_GRID_RES];
	o->name = "magic_lamp1_grid_res";
	o->group = N_("Magic Lamp");
	o->subGroup = N_("Magic Lamp #1");
	o->advanced = False;
	o->shortDesc = N_("Magic Lamp #1 Grid Y Resolution");
	o->longDesc =
			N_
			("Vertex grid resolution for Magic Lamp #1 (Y dimension only). This is the number of points used to define the curves. The higher the number, the smoother the curves. However there will be a loss of performance (CPU usage increases).");
	o->displayHints = "";
	o->type = CompOptionTypeInt;
	o->value.i = ANIM_MAGIC_LAMP1_GRID_RES_DEFAULT;
	o->rest.i.min = ANIM_MAGIC_LAMP1_GRID_RES_MIN;
	o->rest.i.max = ANIM_MAGIC_LAMP1_GRID_RES_MAX;

	o = &as->opt[ANIM_SCREEN_OPTION_MAGIC_LAMP1_MAX_WAVES];
	o->name = "magic_lamp1_max_waves";
	o->group = N_("Magic Lamp");
	o->subGroup = N_("Magic Lamp #1");
	o->advanced = False;
	o->shortDesc = N_("Magic Lamp #1 Max Waves");
	o->longDesc = N_("The maximum number of waves for Magic Lamp #1.");
	o->displayHints = "";
	o->type = CompOptionTypeInt;
	o->value.i = ANIM_MAGIC_LAMP1_MAX_WAVES_DEFAULT;
	o->rest.i.min = ANIM_MAGIC_LAMP1_MAX_WAVES_MIN;
	o->rest.i.max = ANIM_MAGIC_LAMP1_MAX_WAVES_MAX;

	o = &as->opt[ANIM_SCREEN_OPTION_MAGIC_LAMP1_WAVE_AMP_MIN];
	o->name = "magic_lamp1_wave_amp_min";
	o->group = N_("Magic Lamp");
	o->subGroup = N_("Magic Lamp #1");
	o->advanced = False;
	o->shortDesc = N_("Magic Lamp #1 Wave Min Amplitude");
	o->longDesc =
			N_
			("The minimum amplitude (size of the waves) Magic Lamp #1 will have.");
	o->displayHints = "";
	o->type = CompOptionTypeFloat;
	o->value.f = ANIM_MAGIC_LAMP1_WAVE_AMP_MIN_DEFAULT;
	o->rest.f.min = ANIM_MAGIC_LAMP1_WAVE_AMP_MIN_MIN;
	o->rest.f.max = ANIM_MAGIC_LAMP1_WAVE_AMP_MIN_MAX;
	o->rest.f.precision = ANIM_MAGIC_LAMP1_WAVE_AMP_MIN_PRECISION;

	o = &as->opt[ANIM_SCREEN_OPTION_MAGIC_LAMP1_WAVE_AMP_MAX];
	o->name = "magic_lamp1_wave_amp_max";
	o->group = N_("Magic Lamp");
	o->subGroup = N_("Magic Lamp #1");
	o->advanced = False;
	o->shortDesc = N_("Magic Lamp #1 Wave Max Amplitude");
	o->longDesc =
			N_
			("The maxmimum amplitude (size of the waves) Magic Lamp #1 will have.");
	o->displayHints = "";
	o->type = CompOptionTypeFloat;
	o->value.f = ANIM_MAGIC_LAMP1_WAVE_AMP_MAX_DEFAULT;
	o->rest.f.min = ANIM_MAGIC_LAMP1_WAVE_AMP_MAX_MIN;
	o->rest.f.max = ANIM_MAGIC_LAMP1_WAVE_AMP_MAX_MAX;
	o->rest.f.precision = ANIM_MAGIC_LAMP1_WAVE_AMP_MAX_PRECISION;

	o = &as->opt[ANIM_SCREEN_OPTION_MAGIC_LAMP1_CREATE_START_WIDTH];
	o->name = "magic_lamp1_create_start_width";
	o->group = N_("Magic Lamp");
	o->subGroup = N_("Magic Lamp #1");
	o->advanced = False;
	o->shortDesc = N_("Magic Lamp #1 Create Start Width");
	o->longDesc =
			N_
			("Starting width of create effect and ending width of close effect for Magic Lamp #1.");
	o->displayHints = "";
	o->type = CompOptionTypeInt;
	o->value.i = ANIM_MAGIC_LAMP1_CREATE_START_WIDTH_DEFAULT;
	o->rest.i.min = ANIM_MAGIC_LAMP1_CREATE_START_WIDTH_MIN;
	o->rest.i.max = ANIM_MAGIC_LAMP1_CREATE_START_WIDTH_MAX;

	o = &as->opt[ANIM_SCREEN_OPTION_MAGIC_LAMP2_GRID_RES];
	o->name = "magic_lamp2_grid_res";
	o->group = N_("Magic Lamp");
	o->subGroup = N_("Magic Lamp #2");
	o->advanced = False;
	o->shortDesc = N_("Magic Lamp #2 Grid Y Resolution");
	o->longDesc =
			N_
			("Vertex grid resolution for Magic Lamp #2 (Y dimension only). This is the number of points used to define the curves. The higher the number, the smoother the curves. However there will be a loss of performance (CPU usage increases).");
	o->displayHints = "";
	o->type = CompOptionTypeInt;
	o->value.i = ANIM_MAGIC_LAMP2_GRID_RES_DEFAULT;
	o->rest.i.min = ANIM_MAGIC_LAMP2_GRID_RES_MIN;
	o->rest.i.max = ANIM_MAGIC_LAMP2_GRID_RES_MAX;

	o = &as->opt[ANIM_SCREEN_OPTION_MAGIC_LAMP2_MAX_WAVES];
	o->name = "magic_lamp2_max_waves";
	o->group = N_("Magic Lamp");
	o->subGroup = N_("Magic Lamp #2");
	o->advanced = False;
	o->shortDesc = N_("Magic Lamp #2 Max Waves");
	o->longDesc = N_("The maximum number of waves for Magic Lamp #2.");
	o->displayHints = "";
	o->type = CompOptionTypeInt;
	o->value.i = ANIM_MAGIC_LAMP2_MAX_WAVES_DEFAULT;
	o->rest.i.min = ANIM_MAGIC_LAMP2_MAX_WAVES_MIN;
	o->rest.i.max = ANIM_MAGIC_LAMP2_MAX_WAVES_MAX;

	o = &as->opt[ANIM_SCREEN_OPTION_MAGIC_LAMP2_WAVE_AMP_MIN];
	o->name = "magic_lamp2_wave_amp_min";
	o->group = N_("Magic Lamp");
	o->subGroup = N_("Magic Lamp #2");
	o->advanced = False;
	o->shortDesc = N_("Magic Lamp #2 Wave Min Amplitude");
	o->longDesc =
			N_
			("The minimum amplitude (size of the waves) Magic Lamp #2 will have.");
	o->displayHints = "";
	o->type = CompOptionTypeFloat;
	o->value.f = ANIM_MAGIC_LAMP2_WAVE_AMP_MIN_DEFAULT;
	o->rest.f.min = ANIM_MAGIC_LAMP2_WAVE_AMP_MIN_MIN;
	o->rest.f.max = ANIM_MAGIC_LAMP2_WAVE_AMP_MIN_MAX;
	o->rest.f.precision = ANIM_MAGIC_LAMP2_WAVE_AMP_MIN_PRECISION;

	o = &as->opt[ANIM_SCREEN_OPTION_MAGIC_LAMP2_WAVE_AMP_MAX];
	o->name = "magic_lamp2_wave_amp_max";
	o->group = N_("Magic Lamp");
	o->subGroup = N_("Magic Lamp #2");
	o->advanced = False;
	o->shortDesc = N_("Magic Lamp #2 Wave Max Amplitude");
	o->longDesc =
			N_
			("The maxmimum amplitude (size of the waves) Magic Lamp #2 will have.");
	o->displayHints = "";
	o->type = CompOptionTypeFloat;
	o->value.f = ANIM_MAGIC_LAMP2_WAVE_AMP_MAX_DEFAULT;
	o->rest.f.min = ANIM_MAGIC_LAMP2_WAVE_AMP_MAX_MIN;
	o->rest.f.max = ANIM_MAGIC_LAMP2_WAVE_AMP_MAX_MAX;
	o->rest.f.precision = ANIM_MAGIC_LAMP2_WAVE_AMP_MAX_PRECISION;

	o = &as->opt[ANIM_SCREEN_OPTION_MAGIC_LAMP2_CREATE_START_WIDTH];
	o->name = "magic_lamp2_create_start_width";
	o->group = N_("Magic Lamp");
	o->subGroup = N_("Magic Lamp #2");
	o->advanced = False;
	o->shortDesc = N_("Magic Lamp #2 Create Start Width");
	o->longDesc =
			N_
			("Starting width of create effect and ending width of close effect for Magic Lamp #2.");
	o->displayHints = "";
	o->type = CompOptionTypeInt;
	o->value.i = ANIM_MAGIC_LAMP2_CREATE_START_WIDTH_DEFAULT;
	o->rest.i.min = ANIM_MAGIC_LAMP2_CREATE_START_WIDTH_MIN;
	o->rest.i.max = ANIM_MAGIC_LAMP2_CREATE_START_WIDTH_MAX;


	o = &as->opt[ANIM_SCREEN_OPTION_MAGIC_LAMP3_GRID_RES];
	o->name = "magic_lamp3_grid_res";
	o->group = N_("Magic Lamp");
	o->subGroup = N_("Magic Lamp #3");
	o->advanced = False;
	o->shortDesc = N_("Magic Lamp #3 Grid Y Resolution");
	o->longDesc =
			N_
			("Vertex grid resolution for Magic Lamp #3 (Y dimension only). This is the number of points used to define the curves. The higher the number, the smoother the curves. However there will be a loss of performance (CPU usage increases).");
	o->displayHints = "";
	o->type = CompOptionTypeInt;
	o->value.i = ANIM_MAGIC_LAMP3_GRID_RES_DEFAULT;
	o->rest.i.min = ANIM_MAGIC_LAMP3_GRID_RES_MIN;
	o->rest.i.max = ANIM_MAGIC_LAMP3_GRID_RES_MAX;

	o = &as->opt[ANIM_SCREEN_OPTION_MAGIC_LAMP3_MAX_WAVES];
	o->name = "magic_lamp3_max_waves";
	o->group = N_("Magic Lamp");
	o->subGroup = N_("Magic Lamp #3");
	o->advanced = False;
	o->shortDesc = N_("Magic Lamp #3 Max Waves");
	o->longDesc = N_("The maximum number of waves for Magic Lamp #3.");
	o->displayHints = "";
	o->type = CompOptionTypeInt;
	o->value.i = ANIM_MAGIC_LAMP3_MAX_WAVES_DEFAULT;
	o->rest.i.min = ANIM_MAGIC_LAMP3_MAX_WAVES_MIN;
	o->rest.i.max = ANIM_MAGIC_LAMP3_MAX_WAVES_MAX;

	o = &as->opt[ANIM_SCREEN_OPTION_MAGIC_LAMP3_WAVE_AMP_MIN];
	o->name = "magic_lamp3_wave_amp_min";
	o->group = N_("Magic Lamp");
	o->subGroup = N_("Magic Lamp #3");
	o->advanced = False;
	o->shortDesc = N_("Magic Lamp #3 Wave Min Amplitude");
	o->longDesc =
			N_
			("The minimum amplitude (size of the waves) Magic Lamp #3 will have.");
	o->displayHints = "";
	o->type = CompOptionTypeFloat;
	o->value.f = ANIM_MAGIC_LAMP3_WAVE_AMP_MIN_DEFAULT;
	o->rest.f.min = ANIM_MAGIC_LAMP3_WAVE_AMP_MIN_MIN;
	o->rest.f.max = ANIM_MAGIC_LAMP3_WAVE_AMP_MIN_MAX;
	o->rest.f.precision = ANIM_MAGIC_LAMP3_WAVE_AMP_MIN_PRECISION;

	o = &as->opt[ANIM_SCREEN_OPTION_MAGIC_LAMP3_WAVE_AMP_MAX];
	o->name = "magic_lamp3_wave_amp_max";
	o->group = N_("Magic Lamp");
	o->subGroup = N_("Magic Lamp #3");
	o->advanced = False;
	o->shortDesc = N_("Magic Lamp #3 Wave Max Amplitude");
	o->longDesc =
			N_
			("The maxmimum amplitude (size of the waves) Magic Lamp #3 will have.");
	o->displayHints = "";
	o->type = CompOptionTypeFloat;
	o->value.f = ANIM_MAGIC_LAMP3_WAVE_AMP_MAX_DEFAULT;
	o->rest.f.min = ANIM_MAGIC_LAMP3_WAVE_AMP_MAX_MIN;
	o->rest.f.max = ANIM_MAGIC_LAMP3_WAVE_AMP_MAX_MAX;
	o->rest.f.precision = ANIM_MAGIC_LAMP3_WAVE_AMP_MAX_PRECISION;

	o = &as->opt[ANIM_SCREEN_OPTION_MAGIC_LAMP3_CREATE_START_WIDTH];
	o->name = "magic_lamp3_create_start_width";
	o->group = N_("Magic Lamp");
	o->subGroup = N_("Magic Lamp #3");
	o->advanced = False;
	o->shortDesc = N_("Magic Lamp #3 Create Start Width");
	o->longDesc =
			N_
			("Starting width of create effect and ending width of close effect for Magic Lamp #3.");
	o->displayHints = "";
	o->type = CompOptionTypeInt;
	o->value.i = ANIM_MAGIC_LAMP3_CREATE_START_WIDTH_DEFAULT;
	o->rest.i.min = ANIM_MAGIC_LAMP3_CREATE_START_WIDTH_MIN;
	o->rest.i.max = ANIM_MAGIC_LAMP3_CREATE_START_WIDTH_MAX;

	o = &as->opt[ANIM_SCREEN_OPTION_FOCUS_WAVE_WIDTH];
	o->name = "focus_wave_width";
	o->group = N_("Focus Wave");
	o->subGroup = N_("");
	o->advanced = False;
	o->shortDesc = N_("Focus Wave Width");
	o->longDesc =
			N_("The width of the focus wave relative to the window height.");
	o->displayHints = "";
	o->type = CompOptionTypeFloat;
	o->value.f = ANIM_FOCUS_WAVE_WIDTH_DEFAULT;
	o->rest.f.min = ANIM_FOCUS_WAVE_WIDTH_MIN;
	o->rest.f.max = ANIM_FOCUS_WAVE_WIDTH_MAX;
	o->rest.f.precision = ANIM_FOCUS_WAVE_WIDTH_PRECISION;

	o = &as->opt[ANIM_SCREEN_OPTION_FOCUS_WAVE_AMP];
	o->name = "focus_wave_amp";
	o->group = N_("Focus Wave");
	o->subGroup = N_("");
	o->advanced = False;
	o->shortDesc = N_("Focus Wave Amplitude");
	o->longDesc =
			N_
			("The wave amplitude (size of the waves) relative to the window height.");
	o->displayHints = "";
	o->type = CompOptionTypeFloat;
	o->value.f = ANIM_FOCUS_WAVE_AMP_DEFAULT;
	o->rest.f.min = ANIM_FOCUS_WAVE_AMP_MIN;
	o->rest.f.max = ANIM_FOCUS_WAVE_AMP_MAX;
	o->rest.f.precision = ANIM_FOCUS_WAVE_AMP_PRECISION;

	o = &as->opt[ANIM_SCREEN_OPTION_SIDEKICK_NUM_ROTATIONS];
	o->name = "sidekick_num_rotations";
	o->group = N_("Sidekick");
	o->subGroup = N_("");
	o->advanced = False;
	o->shortDesc = N_("Sidekick Number of Rotations");
	o->longDesc =
			N_
			("The number of rotations plus or minus 10% (for randomness) that Sidekick has.");
	o->displayHints = "";
	o->type = CompOptionTypeFloat;
	o->value.f = ANIM_SIDEKICK_NUM_ROTATIONS_DEFAULT;
	o->rest.f.min = ANIM_SIDEKICK_NUM_ROTATIONS_MIN;
	o->rest.f.max = ANIM_SIDEKICK_NUM_ROTATIONS_MAX;
	o->rest.f.precision = ANIM_SIDEKICK_NUM_ROTATIONS_PRECISION;

	// Minimize

	o = &as->opt[ANIM_SCREEN_OPTION_MINIMIZE_EFFECT];
	o->name = "minimize_effect";
	o->group = N_("(Un)Minimize");
	o->subGroup = N_("");
	o->advanced = False;
	o->shortDesc = N_("Minimize Animation");
	o->longDesc = N_("The animation shown when minimizing a window.");
	o->displayHints = "";
	o->type = CompOptionTypeString;
	o->value.s = strdup(allEffectName[ANIM_MINIMIZE_DEFAULT]);
	o->rest.s.string = minimizeEffectName;
	o->rest.s.nString = NUM_MINIMIZE_EFFECT;

	o = &as->opt[ANIM_SCREEN_OPTION_MINIMIZE_WINDOW_TYPE];
	o->name = "minimize_window_types";
	o->group = N_("(Un)Minimize");
	o->subGroup = N_("Minimize");
	o->advanced = False;
	o->shortDesc = N_("Window Types");
	o->longDesc = N_("The window types that will be animated.");
	o->displayHints = "";
	o->type = CompOptionTypeList;
	o->value.list.type = CompOptionTypeString;
	o->value.list.nValue = N_MINIMIZE_DEFAULT_WIN_TYPE;
	o->value.list.value =
			malloc(sizeof(CompOptionValue) * N_MINIMIZE_DEFAULT_WIN_TYPE);
	for (i = 0; i < N_MINIMIZE_DEFAULT_WIN_TYPE; i++)
		o->value.list.value[i].s = strdup(minimizeDefaultWinType[i]);
	o->rest.s.string = (char **)windowTypeString;
	o->rest.s.nString = nWindowTypeString;

	as->minimizeWMask = compWindowTypeMaskFromStringList(&o->value);

	o = &as->opt[ANIM_SCREEN_OPTION_MINIMIZE_DURATION];
	o->name = "minimize_duration";
	o->group = N_("(Un)Minimize");
	o->subGroup = N_("Minimize");
	o->advanced = False;
	o->shortDesc = N_("Animation Duration");
	o->longDesc =
			N_
			("The number of seconds that the Minimize animation will last.");
	o->displayHints = "";
	o->type = CompOptionTypeFloat;
	o->value.f = ANIM_MINIMIZE_DURATION_DEFAULT;
	o->rest.f.min = ANIM_MINIMIZE_DURATION_MIN;
	o->rest.f.max = ANIM_MINIMIZE_DURATION_MAX;
	o->rest.f.precision = ANIM_MINIMIZE_DURATION_PRECISION;

	INIT_RANDOM_EFFECT_LIST
			(ANIM_SCREEN_OPTION_MINIMIZE_RANDOM_EFFECTS,
			 "minimize", "(Un)Minimize", "Minimize", NUM_MINIMIZE_EFFECT,
			 minimizeEffectName);
	STORE_RANDOM_EFFECT_LIST
			(ANIM_SCREEN_OPTION_MINIMIZE_RANDOM_EFFECTS,
			 NUM_MINIMIZE_EFFECT,
			 ANIM_MINIMIZE_DEFAULT,
			 nMinimizeRandomEffects,
			 minimizeRandomEffects, minimizeEffectName, minimizeEffectType);

	// Unminimize

	o = &as->opt[ANIM_SCREEN_OPTION_UNMINIMIZE_EFFECT];
	o->name = "unminimize_effect";
	o->group = N_("(Un)Minimize");
	o->subGroup = N_("");
	o->advanced = False;
	o->shortDesc = N_("Unminimize Animation");
	o->longDesc = N_("The animation shown when unminimizing a window.");
	o->displayHints = "";
	o->type = CompOptionTypeString;
	o->value.s = strdup(allEffectName[ANIM_UNMINIMIZE_DEFAULT]);
	o->rest.s.string = minimizeEffectName;
	o->rest.s.nString = NUM_MINIMIZE_EFFECT;

	o = &as->opt[ANIM_SCREEN_OPTION_UNMINIMIZE_WINDOW_TYPE];
	o->name = "unminimize_window_types";
	o->group = N_("(Un)Minimize");
	o->subGroup = N_("Unminimize");
	o->advanced = False;
	o->shortDesc = N_("Window Types");
	o->longDesc = N_("The window types that will be animated.");
	o->displayHints = "";
	o->type = CompOptionTypeList;
	o->value.list.type = CompOptionTypeString;
	o->value.list.nValue = N_MINIMIZE_DEFAULT_WIN_TYPE;
	o->value.list.value =
			malloc(sizeof(CompOptionValue) * N_MINIMIZE_DEFAULT_WIN_TYPE);
	for (i = 0; i < N_MINIMIZE_DEFAULT_WIN_TYPE; i++)
		o->value.list.value[i].s = strdup(minimizeDefaultWinType[i]);
	o->rest.s.string = (char **)windowTypeString;
	o->rest.s.nString = nWindowTypeString;

	as->unminimizeWMask = compWindowTypeMaskFromStringList(&o->value);

	o = &as->opt[ANIM_SCREEN_OPTION_UNMINIMIZE_DURATION];
	o->name = "unminimize_duration";
	o->group = N_("(Un)Minimize");
	o->subGroup = N_("Unminimize");
	o->advanced = False;
	o->shortDesc = N_("Animation Duration");
	o->longDesc =
			N_
			("The number of seconds that the Unminimize animation will last.");
	o->displayHints = "";
	o->type = CompOptionTypeFloat;
	o->value.f = ANIM_UNMINIMIZE_DURATION_DEFAULT;
	o->rest.f.min = ANIM_UNMINIMIZE_DURATION_MIN;
	o->rest.f.max = ANIM_UNMINIMIZE_DURATION_MAX;
	o->rest.f.precision = ANIM_UNMINIMIZE_DURATION_PRECISION;

	INIT_RANDOM_EFFECT_LIST
			(ANIM_SCREEN_OPTION_UNMINIMIZE_RANDOM_EFFECTS,
			 "unminimize", "(Un)Minimize", "Unminimize", NUM_MINIMIZE_EFFECT,
			 minimizeEffectName);
	STORE_RANDOM_EFFECT_LIST
			(ANIM_SCREEN_OPTION_UNMINIMIZE_RANDOM_EFFECTS,
			 NUM_MINIMIZE_EFFECT,
			 ANIM_UNMINIMIZE_DEFAULT,
			 nUnminimizeRandomEffects,
			 unminimizeRandomEffects, minimizeEffectName, minimizeEffectType);

	// Close 1

	o = &as->opt[ANIM_SCREEN_OPTION_CLOSE1_EFFECT];
	o->name = "close1_effect";
	o->group = N_("Close");
	o->subGroup = N_("");
	o->advanced = False;
	o->shortDesc = N_("Close #1 Animation");
	o->longDesc = N_("The animation shown when closing a window.");
	o->displayHints = "";
	o->type = CompOptionTypeString;
	o->value.s = strdup(allEffectName[ANIM_CLOSE1_DEFAULT]);
	o->rest.s.string = closeEffectName;
	o->rest.s.nString = NUM_CLOSE_EFFECT;

	o = &as->opt[ANIM_SCREEN_OPTION_CLOSE1_WINDOW_TYPE];
	o->name = "close1_window_types";
	o->group = N_("Close");
	o->subGroup = N_("Close #1");
	o->advanced = False;
	o->shortDesc = N_("Window Types");
	o->longDesc = N_("The window types that will be animated.");
	o->displayHints = "";
	o->type = CompOptionTypeList;
	o->value.list.type = CompOptionTypeString;
	o->value.list.nValue = N_CLOSE1_DEFAULT_WIN_TYPE;
	o->value.list.value =
			malloc(sizeof(CompOptionValue) * N_CLOSE1_DEFAULT_WIN_TYPE);
	for (i = 0; i < N_CLOSE1_DEFAULT_WIN_TYPE; i++)
		o->value.list.value[i].s = strdup(close1DefaultWinType[i]);
	o->rest.s.string = (char **)windowTypeString;
	o->rest.s.nString = nWindowTypeString;

	as->close1WMask = compWindowTypeMaskFromStringList(&o->value);

	o = &as->opt[ANIM_SCREEN_OPTION_CLOSE1_DURATION];
	o->name = "close1_duration";
	o->group = N_("Close");
	o->subGroup = N_("Close #1");
	o->advanced = False;
	o->shortDesc = N_("Animation Duration");
	o->longDesc =
			N_
			("The number of seconds that the Close #1 animation will last.");
	o->displayHints = "";
	o->type = CompOptionTypeFloat;
	o->value.f = ANIM_CLOSE1_DURATION_DEFAULT;
	o->rest.f.min = ANIM_CLOSE1_DURATION_MIN;
	o->rest.f.max = ANIM_CLOSE1_DURATION_MAX;
	o->rest.f.precision = ANIM_CLOSE1_DURATION_PRECISION;

	INIT_RANDOM_EFFECT_LIST
			(ANIM_SCREEN_OPTION_CLOSE1_RANDOM_EFFECTS,
			 "close1", "Close", "Close #1", NUM_CLOSE_EFFECT,
			 closeEffectName);
	STORE_RANDOM_EFFECT_LIST
			(ANIM_SCREEN_OPTION_CLOSE1_RANDOM_EFFECTS,
			 NUM_CLOSE_EFFECT,
			 ANIM_CLOSE1_DEFAULT,
			 nClose1RandomEffects,
			 close1RandomEffects, closeEffectName, closeEffectType);

	// Close 2

	o = &as->opt[ANIM_SCREEN_OPTION_CLOSE2_EFFECT];
	o->name = "close2_effect";
	o->group = N_("Close");
	o->subGroup = N_("");
	o->advanced = False;
	o->shortDesc = N_("Close #2 Animation");
	o->longDesc = N_("The animation shown when closing a window.");
	o->displayHints = "";
	o->type = CompOptionTypeString;
	o->value.s = strdup(allEffectName[ANIM_CLOSE2_DEFAULT]);
	o->rest.s.string = closeEffectName;
	o->rest.s.nString = NUM_CLOSE_EFFECT;

	o = &as->opt[ANIM_SCREEN_OPTION_CLOSE2_WINDOW_TYPE];
	o->name = "close2_window_types";
	o->group = N_("Close");
	o->subGroup = N_("Close #2");
	o->advanced = False;
	o->shortDesc = N_("Window Types");
	o->longDesc = N_("The window types that will be animated.");
	o->displayHints = "";
	o->type = CompOptionTypeList;
	o->value.list.type = CompOptionTypeString;
	o->value.list.nValue = N_CLOSE2_DEFAULT_WIN_TYPE;
	o->value.list.value =
			malloc(sizeof(CompOptionValue) * N_CLOSE2_DEFAULT_WIN_TYPE);
	for (i = 0; i < N_CLOSE2_DEFAULT_WIN_TYPE; i++)
		o->value.list.value[i].s = strdup(close2DefaultWinType[i]);
	o->rest.s.string = (char **)windowTypeString;
	o->rest.s.nString = nWindowTypeString;

	as->close2WMask = compWindowTypeMaskFromStringList(&o->value);

	o = &as->opt[ANIM_SCREEN_OPTION_CLOSE2_DURATION];
	o->name = "close2_duration";
	o->group = N_("Close");
	o->subGroup = N_("Close #2");
	o->advanced = False;
	o->shortDesc = N_("Animation Duration");
	o->longDesc =
			N_
			("The number of seconds that the Close #2 animation will last.");
	o->displayHints = "";
	o->type = CompOptionTypeFloat;
	o->value.f = ANIM_CLOSE2_DURATION_DEFAULT;
	o->rest.f.min = ANIM_CLOSE2_DURATION_MIN;
	o->rest.f.max = ANIM_CLOSE2_DURATION_MAX;
	o->rest.f.precision = ANIM_CLOSE2_DURATION_PRECISION;

	INIT_RANDOM_EFFECT_LIST
			(ANIM_SCREEN_OPTION_CLOSE2_RANDOM_EFFECTS,
			 "close2", "Close", "Close #2", NUM_CLOSE_EFFECT,
			 closeEffectName);
	STORE_RANDOM_EFFECT_LIST
			(ANIM_SCREEN_OPTION_CLOSE2_RANDOM_EFFECTS,
			 NUM_CLOSE_EFFECT,
			 ANIM_CLOSE2_DEFAULT,
			 nClose2RandomEffects,
			 close2RandomEffects, closeEffectName, closeEffectType);

	// Create 1

	o = &as->opt[ANIM_SCREEN_OPTION_CREATE1_EFFECT];
	o->name = "create1_effect";
	o->group = N_("Create");
	o->subGroup = N_("");
	o->advanced = False;
	o->shortDesc = N_("Create #1 Animation");
	o->longDesc = N_("The animation shown when creating a window.");
	o->displayHints = "";
	o->type = CompOptionTypeString;
	o->value.s = strdup(allEffectName[ANIM_CREATE1_DEFAULT]);
	o->rest.s.string = closeEffectName;
	o->rest.s.nString = NUM_CLOSE_EFFECT;

	o = &as->opt[ANIM_SCREEN_OPTION_CREATE1_WINDOW_TYPE];
	o->name = "create1_window_types";
	o->group = N_("Create");
	o->subGroup = N_("Create #1");
	o->advanced = False;
	o->shortDesc = N_("Window Types");
	o->longDesc =
			N_
			("Window types that should animate with this effect when created.");
	o->displayHints = "";
	o->type = CompOptionTypeList;
	o->value.list.type = CompOptionTypeString;
	o->value.list.nValue = N_CLOSE1_DEFAULT_WIN_TYPE;
	o->value.list.value =
			malloc(sizeof(CompOptionValue) * N_CLOSE1_DEFAULT_WIN_TYPE);
	for (i = 0; i < N_CLOSE1_DEFAULT_WIN_TYPE; i++)
		o->value.list.value[i].s = strdup(close1DefaultWinType[i]);
	o->rest.s.string = (char **)windowTypeString;
	o->rest.s.nString = nWindowTypeString;

	as->create1WMask = compWindowTypeMaskFromStringList(&o->value);

	o = &as->opt[ANIM_SCREEN_OPTION_CREATE1_DURATION];
	o->name = "create1_duration";
	o->group = N_("Create");
	o->subGroup = N_("Create #1");
	o->advanced = False;
	o->shortDesc = N_("Animation Duration");
	o->longDesc = N_("Animation duration in seconds for create effect #1.");
	o->displayHints = "";
	o->type = CompOptionTypeFloat;
	o->value.f = ANIM_CREATE1_DURATION_DEFAULT;
	o->rest.f.min = ANIM_CREATE1_DURATION_MIN;
	o->rest.f.max = ANIM_CREATE1_DURATION_MAX;
	o->rest.f.precision = ANIM_CREATE1_DURATION_PRECISION;

	INIT_RANDOM_EFFECT_LIST
			(ANIM_SCREEN_OPTION_CREATE1_RANDOM_EFFECTS,
			 "create1", "Create", "Create #1", NUM_CLOSE_EFFECT,
			 closeEffectName);
	STORE_RANDOM_EFFECT_LIST
			(ANIM_SCREEN_OPTION_CREATE1_RANDOM_EFFECTS,
			 NUM_CLOSE_EFFECT,
			 ANIM_CREATE1_DEFAULT,
			 nCreate1RandomEffects,
			 create1RandomEffects, closeEffectName, closeEffectType);

	// Create 2

	o = &as->opt[ANIM_SCREEN_OPTION_CREATE2_EFFECT];
	o->name = "create2_effect";
	o->group = N_("Create");
	o->subGroup = N_("");
	o->advanced = False;
	o->shortDesc = N_("Create #2 Animation");
	o->longDesc = N_("The animation shown when creating a window.");
	o->displayHints = "";
	o->type = CompOptionTypeString;
	o->value.s = strdup(allEffectName[ANIM_CREATE2_DEFAULT]);
	o->rest.s.string = closeEffectName;
	o->rest.s.nString = NUM_CLOSE_EFFECT;

	o = &as->opt[ANIM_SCREEN_OPTION_CREATE2_WINDOW_TYPE];
	o->name = "create2_window_types";
	o->group = N_("Create");
	o->subGroup = N_("Create #2");
	o->advanced = False;
	o->shortDesc = N_("Window Types");
	o->longDesc =
			N_
			("Window types that should animate with this effect when created.");
	o->displayHints = "";
	o->type = CompOptionTypeList;
	o->value.list.type = CompOptionTypeString;
	o->value.list.nValue = N_CLOSE2_DEFAULT_WIN_TYPE;
	o->value.list.value =
			malloc(sizeof(CompOptionValue) * N_CLOSE2_DEFAULT_WIN_TYPE);
	for (i = 0; i < N_CLOSE2_DEFAULT_WIN_TYPE; i++)
		o->value.list.value[i].s = strdup(close2DefaultWinType[i]);
	o->rest.s.string = (char **)windowTypeString;
	o->rest.s.nString = nWindowTypeString;

	as->create2WMask = compWindowTypeMaskFromStringList(&o->value);

	o = &as->opt[ANIM_SCREEN_OPTION_CREATE2_DURATION];
	o->name = "create2_duration";
	o->group = N_("Create");
	o->subGroup = N_("Create #2");
	o->advanced = False;
	o->shortDesc = N_("Animation Duration");
	o->longDesc = N_("Animation duration in seconds for create effect #2.");
	o->displayHints = "";
	o->type = CompOptionTypeFloat;
	o->value.f = ANIM_CREATE2_DURATION_DEFAULT;
	o->rest.f.min = ANIM_CREATE2_DURATION_MIN;
	o->rest.f.max = ANIM_CREATE2_DURATION_MAX;
	o->rest.f.precision = ANIM_CREATE2_DURATION_PRECISION;

	INIT_RANDOM_EFFECT_LIST
			(ANIM_SCREEN_OPTION_CREATE2_RANDOM_EFFECTS,
			 "create2", "Create", "Create #2", NUM_CLOSE_EFFECT,
			 closeEffectName);
	STORE_RANDOM_EFFECT_LIST
			(ANIM_SCREEN_OPTION_CREATE2_RANDOM_EFFECTS,
			 NUM_CLOSE_EFFECT,
			 ANIM_CREATE2_DEFAULT,
			 nCreate2RandomEffects,
			 create2RandomEffects, closeEffectName, closeEffectType);

	// Focus

	o = &as->opt[ANIM_SCREEN_OPTION_FOCUS_EFFECT];
	o->name = "focus_effect";
	o->group = N_("Focus");
	o->subGroup = N_("");
	o->advanced = False;
	o->shortDesc = N_("Animation");
	o->longDesc = N_("Focus window effect.");
	o->displayHints = "";
	o->type = CompOptionTypeString;
	o->value.s = strdup(allEffectName[ANIM_FOCUS_DEFAULT]);
	o->rest.s.string = focusEffectName;
	o->rest.s.nString = NUM_FOCUS_EFFECT;

	o = &as->opt[ANIM_SCREEN_OPTION_FOCUS_WINDOW_TYPE];
	o->name = "focus_window_types";
	o->group = N_("Focus");
	o->subGroup = N_("");
	o->advanced = False;
	o->shortDesc = N_("Window Types");
	o->longDesc =
			N_
			("Window types that should animate with this effect when focused.");
	o->displayHints = "";
	o->type = CompOptionTypeList;
	o->value.list.type = CompOptionTypeString;
	o->value.list.nValue = N_FOCUS_DEFAULT_WIN_TYPE;
	o->value.list.value =
			malloc(sizeof(CompOptionValue) * N_FOCUS_DEFAULT_WIN_TYPE);
	for (i = 0; i < N_FOCUS_DEFAULT_WIN_TYPE; i++)
		o->value.list.value[i].s = strdup(focusDefaultWinType[i]);
	o->rest.s.string = (char **)windowTypeString;
	o->rest.s.nString = nWindowTypeString;

	as->focusWMask = compWindowTypeMaskFromStringList(&o->value);

	o = &as->opt[ANIM_SCREEN_OPTION_FOCUS_DURATION];
	o->name = "focus_duration";
	o->group = N_("Focus");
	o->subGroup = N_("");
	o->advanced = False;
	o->shortDesc = N_("Animation Duration");
	o->longDesc = N_("Focus animation duration in seconds.");
	o->displayHints = "";
	o->type = CompOptionTypeFloat;
	o->value.f = ANIM_FOCUS_DURATION_DEFAULT;
	o->rest.f.min = ANIM_FOCUS_DURATION_MIN;
	o->rest.f.max = ANIM_FOCUS_DURATION_MAX;
	o->rest.f.precision = ANIM_FOCUS_DURATION_PRECISION;

	// Shade

	o = &as->opt[ANIM_SCREEN_OPTION_SHADE_EFFECT];
	o->name = "shade_effect";
	o->group = N_("(Un)Shade");
	o->subGroup = N_("");
	o->advanced = False;
	o->shortDesc = N_("Shade Animation");
	o->longDesc = N_("Shade window effect.");
	o->displayHints = "";
	o->type = CompOptionTypeString;
	o->value.s = strdup(allEffectName[ANIM_SHADE_DEFAULT]);
	o->rest.s.string = shadeEffectName;
	o->rest.s.nString = NUM_SHADE_EFFECT;

	o = &as->opt[ANIM_SCREEN_OPTION_SHADE_WINDOW_TYPE];
	o->name = "shade_window_types";
	o->group = N_("(Un)Shade");
	o->subGroup = N_("Shade");
	o->advanced = False;
	o->shortDesc = N_("Window Types");
	o->longDesc =
			N_
			("Window types that should animate with this effect when shaded.");
	o->displayHints = "";
	o->type = CompOptionTypeList;
	o->value.list.type = CompOptionTypeString;
	o->value.list.nValue = N_SHADE_DEFAULT_WIN_TYPE;
	o->value.list.value =
			malloc(sizeof(CompOptionValue) * N_SHADE_DEFAULT_WIN_TYPE);
	for (i = 0; i < N_SHADE_DEFAULT_WIN_TYPE; i++)
		o->value.list.value[i].s = strdup(shadeDefaultWinType[i]);
	o->rest.s.string = (char **)windowTypeString;
	o->rest.s.nString = nWindowTypeString;

	as->shadeWMask = compWindowTypeMaskFromStringList(&o->value);

	o = &as->opt[ANIM_SCREEN_OPTION_SHADE_DURATION];
	o->name = "shade_duration";
	o->group = N_("(Un)Shade");
	o->subGroup = N_("Shade");
	o->advanced = False;
	o->shortDesc = N_("Animation Duration");
	o->longDesc = N_("Shade animation duration in seconds.");
	o->displayHints = "";
	o->type = CompOptionTypeFloat;
	o->value.f = ANIM_SHADE_DURATION_DEFAULT;
	o->rest.f.min = ANIM_SHADE_DURATION_MIN;
	o->rest.f.max = ANIM_SHADE_DURATION_MAX;
	o->rest.f.precision = ANIM_SHADE_DURATION_PRECISION;

	INIT_RANDOM_EFFECT_LIST
			(ANIM_SCREEN_OPTION_SHADE_RANDOM_EFFECTS,
			 "shade", "(Un)Shade", "Shade", NUM_SHADE_EFFECT,
			 shadeEffectName);
	STORE_RANDOM_EFFECT_LIST
			(ANIM_SCREEN_OPTION_SHADE_RANDOM_EFFECTS,
			 NUM_SHADE_EFFECT,
			 ANIM_SHADE_DEFAULT,
			 nShadeRandomEffects,
			 shadeRandomEffects, shadeEffectName, shadeEffectType);

	// Unshade

	o = &as->opt[ANIM_SCREEN_OPTION_UNSHADE_EFFECT];
	o->name = "unshade_effect";
	o->group = N_("(Un)Shade");
	o->subGroup = N_("");
	o->advanced = False;
	o->shortDesc = N_("Unshade Animation");
	o->longDesc = N_("Unshade window effect.");
	o->displayHints = "";
	o->type = CompOptionTypeString;
	o->value.s = strdup(allEffectName[ANIM_UNSHADE_DEFAULT]);
	o->rest.s.string = shadeEffectName;
	o->rest.s.nString = NUM_SHADE_EFFECT;

	o = &as->opt[ANIM_SCREEN_OPTION_UNSHADE_WINDOW_TYPE];
	o->name = "unshade_window_types";
	o->group = N_("(Un)Shade");
	o->subGroup = N_("Unshade");
	o->advanced = False;
	o->shortDesc = N_("Window Types");
	o->longDesc =
			N_
			("Window types that should animate with this effect when unshaded.");
	o->displayHints = "";
	o->type = CompOptionTypeList;
	o->value.list.type = CompOptionTypeString;
	o->value.list.nValue = N_SHADE_DEFAULT_WIN_TYPE;
	o->value.list.value =
			malloc(sizeof(CompOptionValue) * N_SHADE_DEFAULT_WIN_TYPE);
	for (i = 0; i < N_SHADE_DEFAULT_WIN_TYPE; i++)
		o->value.list.value[i].s = strdup(shadeDefaultWinType[i]);
	o->rest.s.string = (char **)windowTypeString;
	o->rest.s.nString = nWindowTypeString;

	as->unshadeWMask = compWindowTypeMaskFromStringList(&o->value);

	o = &as->opt[ANIM_SCREEN_OPTION_UNSHADE_DURATION];
	o->name = "unshade_duration";
	o->group = N_("(Un)Shade");
	o->subGroup = N_("Unshade");
	o->advanced = False;
	o->shortDesc = N_("Animation Duration");
	o->longDesc = N_("Unshade animation duration in seconds.");
	o->displayHints = "";
	o->type = CompOptionTypeFloat;
	o->value.f = ANIM_UNSHADE_DURATION_DEFAULT;
	o->rest.f.min = ANIM_UNSHADE_DURATION_MIN;
	o->rest.f.max = ANIM_UNSHADE_DURATION_MAX;
	o->rest.f.precision = ANIM_UNSHADE_DURATION_PRECISION;

	INIT_RANDOM_EFFECT_LIST
			(ANIM_SCREEN_OPTION_UNSHADE_RANDOM_EFFECTS,
			 "unshade", "(Un)Shade", "Unshade", NUM_SHADE_EFFECT,
			 shadeEffectName);
	STORE_RANDOM_EFFECT_LIST
			(ANIM_SCREEN_OPTION_UNSHADE_RANDOM_EFFECTS,
			 NUM_SHADE_EFFECT,
			 ANIM_UNSHADE_DEFAULT,
			 nUnshadeRandomEffects,
			 unshadeRandomEffects, shadeEffectName, shadeEffectType);

	// Fire (Burn)

	o = &as->opt[ANIM_SCREEN_OPTION_FIRE_PARTICLES];
	o->name = "fire_particles";
	o->group = N_("Fire (A.K.A Burn)");
	o->subGroup = N_("");
	o->advanced = False;
	o->shortDesc = N_("Number Of Fire Particles");
	o->longDesc = N_("Number of fire particles.");
	o->displayHints = "";
	o->type = CompOptionTypeInt;
	o->value.i = ANIM_FIRE_PARTICLES_DEFAULT;
	o->rest.i.min = ANIM_FIRE_PARTICLES_MIN;
	o->rest.i.max = ANIM_FIRE_PARTICLES_MAX;

	o = &as->opt[ANIM_SCREEN_OPTION_FIRE_SIZE];
	o->name = "fire_size";
	o->group = N_("Fire (A.K.A Burn)");
	o->subGroup = N_("");
	o->advanced = False;
	o->shortDesc = N_("Fire Particle Size");
	o->longDesc = N_("Fire particle size.");
	o->displayHints = "";
	o->type = CompOptionTypeFloat;
	o->value.f = ANIM_FIRE_SIZE_DEFAULT;
	o->rest.f.min = ANIM_FIRE_SIZE_MIN;
	o->rest.f.max = ANIM_FIRE_SIZE_MAX;
	o->rest.f.precision = ANIM_FIRE_SIZE_PRECISION;

	o = &as->opt[ANIM_SCREEN_OPTION_FIRE_SLOWDOWN];
	o->name = "fire_slowdown";
	o->group = N_("Fire (A.K.A Burn)");
	o->subGroup = N_("");
	o->advanced = False;
	o->shortDesc = N_("Fire Particle Slowdown");
	o->longDesc = N_("Fire particle slowdown.");
	o->displayHints = "";
	o->type = CompOptionTypeFloat;
	o->value.f = ANIM_FIRE_SLOWDOWN_DEFAULT;
	o->rest.f.min = ANIM_FIRE_SLOWDOWN_MIN;
	o->rest.f.max = ANIM_FIRE_SLOWDOWN_MAX;
	o->rest.f.precision = ANIM_FIRE_SLOWDOWN_PRECISION;

	o = &as->opt[ANIM_SCREEN_OPTION_FIRE_LIFE];
	o->name = "fire_life";
	o->group = N_("Fire (A.K.A Burn)");
	o->subGroup = N_("");
	o->advanced = False;
	o->shortDesc = N_("Fire Particle Life");
	o->longDesc = N_("Fire particle life.");
	o->displayHints = "";
	o->type = CompOptionTypeFloat;
	o->value.f = ANIM_FIRE_LIFE_DEFAULT;
	o->rest.f.min = ANIM_FIRE_LIFE_MIN;
	o->rest.f.max = ANIM_FIRE_LIFE_MAX;
	o->rest.f.precision = ANIM_FIRE_LIFE_PRECISION;

	o = &as->opt[ANIM_SCREEN_OPTION_FIRE_COLOR];
	o->name = "fire_color";
	o->group = N_("Fire (A.K.A Burn)");
	o->subGroup = N_("");
	o->advanced = False;
	o->shortDesc = N_("Fire Particle Color");
	o->longDesc = N_("Fire particle color.");
	o->displayHints = "";
	o->type = CompOptionTypeColor;
	o->value.c[0] = ANIM_FIRE_COLOR_RED_DEFAULT;
	o->value.c[1] = ANIM_FIRE_COLOR_GREEN_DEFAULT;
	o->value.c[2] = ANIM_FIRE_COLOR_BLUE_DEFAULT;
	o->value.c[3] = ANIM_FIRE_COLOR_ALPHA_DEFAULT;

	o = &as->opt[ANIM_SCREEN_OPTION_FIRE_DIRECTION];
	o->name = "fire_direction";
	o->group = N_("Fire (A.K.A Burn)");
	o->subGroup = N_("");
	o->advanced = False;
	o->shortDesc = N_("Fire direction");
	o->longDesc = N_("Fire direction.");
	o->displayHints = "";
	o->type = CompOptionTypeString;
	o->value.s = strdup(animDirectionName[ANIM_FIRE_DIRECTION_DEFAULT]);
	o->rest.s.string = animDirectionName;
	o->rest.s.nString = LIST_SIZE(animDirectionName);

	o = &as->opt[ANIM_SCREEN_OPTION_FIRE_CONSTANT_SPEED];
	o->name = "fire_constant_speed";
	o->group = N_("Fire (A.K.A Burn)");
	o->subGroup = N_("");
	o->advanced = False;
	o->shortDesc = N_("Fire constant speed");
	o->longDesc =
			N_("Make fire effect duration be dependent on window height.");
	o->displayHints = "";
	o->type = CompOptionTypeBool;
	o->value.b = ANIM_FIRE_CONSTANT_SPEED_DEFAULT;

	o = &as->opt[ANIM_SCREEN_OPTION_FIRE_SMOKE];
	o->name = "fire_smoke";
	o->group = N_("Fire (A.K.A Burn)");
	o->subGroup = N_("");
	o->advanced = False;
	o->shortDesc = N_("Fire Smoke");
	o->longDesc = N_("Fire smoke.");
	o->displayHints = "";
	o->type = CompOptionTypeBool;
	o->value.b = ANIM_FIRE_SMOKE_DEFAULT;

	o = &as->opt[ANIM_SCREEN_OPTION_FIRE_MYSTICAL];
	o->name = "fire_mystical";
	o->group = N_("Fire (A.K.A Burn)");
	o->subGroup = N_("");
	o->advanced = False;
	o->shortDesc = N_("Randomly Colored Fire");
	o->longDesc =
			N_
			("Have random colors for the fire effect, also known as Mystical Fire.");
	o->displayHints = "";
	o->type = CompOptionTypeBool;
	o->value.b = ANIM_FIRE_MYSTICAL_DEFAULT;

	// Beam Up

	o = &as->opt[ANIM_SCREEN_OPTION_BEAMUP_SIZE];
	o->name = "beam_size";
	o->group = N_("Beam");
	o->subGroup = N_("");
	o->advanced = False;
	o->shortDesc = N_("Beam Width");
	o->longDesc = N_("Beam width.");
	o->displayHints = "";
	o->type = CompOptionTypeFloat;
	o->value.f = ANIM_BEAMUP_SIZE_DEFAULT;
	o->rest.f.min = ANIM_BEAMUP_SIZE_MIN;
	o->rest.f.max = ANIM_BEAMUP_SIZE_MAX;
	o->rest.f.precision = ANIM_BEAMUP_SIZE_PRECISION;

	o = &as->opt[ANIM_SCREEN_OPTION_BEAMUP_SPACING];
	o->name = "beam_spacing";
	o->group = N_("Beam");
	o->subGroup = N_("");
	o->advanced = False;
	o->shortDesc = N_("Beam spacing");
	o->longDesc = N_("Spacing between beams.");
	o->displayHints = "";
	o->type = CompOptionTypeInt;
	o->value.i = ANIM_BEAMUP_SPACING_DEFAULT;
	o->rest.i.min = ANIM_BEAMUP_SPACING_MIN;
	o->rest.i.max = ANIM_BEAMUP_SPACING_MAX;

	o = &as->opt[ANIM_SCREEN_OPTION_BEAMUP_COLOR];
	o->name = "beam_color";
	o->group = N_("Beam");
	o->subGroup = N_("");
	o->advanced = False;
	o->shortDesc = N_("Beam Color");
	o->longDesc = N_("Beam color.");
	o->displayHints = "";
	o->type = CompOptionTypeColor;
	o->value.c[0] = ANIM_BEAMUP_COLOR_RED_DEFAULT;
	o->value.c[1] = ANIM_BEAMUP_COLOR_GREEN_DEFAULT;
	o->value.c[2] = ANIM_BEAMUP_COLOR_BLUE_DEFAULT;
	o->value.c[3] = ANIM_BEAMUP_COLOR_ALPHA_DEFAULT;

	o = &as->opt[ANIM_SCREEN_OPTION_BEAMUP_SLOWDOWN];
	o->name = "beam_slowdown";
	o->group = N_("Beam");
	o->subGroup = N_("");
	o->advanced = False;
	o->shortDesc = N_("Beam Slowdown");
	o->longDesc = N_("Beam slowdown.");
	o->displayHints = "";
	o->type = CompOptionTypeFloat;
	o->value.f = ANIM_BEAMUP_SLOWDOWN_DEFAULT;
	o->rest.f.min = ANIM_BEAMUP_SLOWDOWN_MIN;
	o->rest.f.max = ANIM_BEAMUP_SLOWDOWN_MAX;
	o->rest.f.precision = ANIM_BEAMUP_SLOWDOWN_PRECISION;

	o = &as->opt[ANIM_SCREEN_OPTION_BEAMUP_LIFE];
	o->name = "beam_life";
	o->group = N_("Beam");
	o->subGroup = N_("");
	o->advanced = False;
	o->shortDesc = N_("Beam Life");
	o->longDesc = N_("Beam life.");
	o->displayHints = "";
	o->type = CompOptionTypeFloat;
	o->value.f = ANIM_BEAMUP_LIFE_DEFAULT;
	o->rest.f.min = ANIM_BEAMUP_LIFE_MIN;
	o->rest.f.max = ANIM_BEAMUP_LIFE_MAX;
	o->rest.f.precision = ANIM_BEAMUP_LIFE_PRECISION;

	// Domino / Razr

	o = &as->opt[ANIM_SCREEN_OPTION_DOMINO_DIRECTION];
	o->name = "domino_direction";
	o->group = N_("Domino/Razr");
	o->subGroup = N_("");
	o->advanced = False;
	o->shortDesc = N_("Domino Piece Falling Direction");
	o->longDesc = N_("Falling direction for Domino pieces.");
	o->displayHints = "";
	o->type = CompOptionTypeString;
	o->value.s = strdup(animDirectionName[ANIM_DOMINO_DIRECTION_DEFAULT]);
	o->rest.s.string = animDirectionName;
	o->rest.s.nString = LIST_SIZE(animDirectionName);

	o = &as->opt[ANIM_SCREEN_OPTION_RAZR_DIRECTION];
	o->name = "razr_direction";
	o->group = N_("Domino/Razr");
	o->subGroup = N_("");
	o->advanced = False;
	o->shortDesc = N_("Razr Fold Opening Direction");
	o->longDesc = N_("Fold opening direction for pieces in Razr effect.");
	o->displayHints = "";
	o->type = CompOptionTypeString;
	o->value.s = strdup(animDirectionName[ANIM_RAZR_DIRECTION_DEFAULT]);
	o->rest.s.string = animDirectionName;
	o->rest.s.nString = LIST_SIZE(animDirectionName);

	// Glide 1

	o = &as->opt[ANIM_SCREEN_OPTION_GLIDE1_AWAY_POS];
	o->name = "glide1_away_position";
	o->group = N_("Glide");
	o->subGroup = N_("Glide 1");
	o->advanced = False;
	o->shortDesc = N_("Away Position");
	o->longDesc = N_("Closeness of window to camera at the end of the "
					 "animation "
					 "(1.0: Close to camera, -2.0: Away from camera).");
	o->displayHints = "";
	o->type = CompOptionTypeFloat;
	o->value.f = ANIM_GLIDE1_AWAY_POS_DEFAULT;
	o->rest.f.min = ANIM_GLIDE1_AWAY_POS_MIN;
	o->rest.f.max = ANIM_GLIDE1_AWAY_POS_MAX;
	o->rest.f.precision = ANIM_GLIDE1_AWAY_POS_PRECISION;

	o = &as->opt[ANIM_SCREEN_OPTION_GLIDE1_AWAY_ANGLE];
	o->name = "glide1_away_angle";
	o->group = N_("Glide");
	o->subGroup = N_("Glide 1");
	o->advanced = False;
	o->shortDesc = N_("Away Angle");
	o->longDesc = N_("Angle of window at the end of the animation.");
	o->displayHints = "";
	o->type = CompOptionTypeFloat;
	o->value.f = ANIM_GLIDE1_AWAY_ANGLE_DEFAULT;
	o->rest.f.min = ANIM_GLIDE1_AWAY_ANGLE_MIN;
	o->rest.f.max = ANIM_GLIDE1_AWAY_ANGLE_MAX;
	o->rest.f.precision = ANIM_GLIDE1_AWAY_ANGLE_PRECISION;

	o = &as->opt[ANIM_SCREEN_OPTION_GLIDE1_THICKNESS];
	o->name = "glide1_thickness";
	o->group = N_("Glide");
	o->subGroup = N_("Glide 1");
	o->advanced = False;
	o->shortDesc = N_("Thickness");
	o->longDesc = N_("Window thickness in pixels. Setting this to larger "
					 "than 0 will disable shadow, blur, and reflection "
					 "during the animation.");
	o->displayHints = "";
	o->type = CompOptionTypeFloat;
	o->value.f = ANIM_GLIDE1_THICKNESS_DEFAULT;
	o->rest.f.min = ANIM_GLIDE1_THICKNESS_MIN;
	o->rest.f.max = ANIM_GLIDE1_THICKNESS_MAX;
	o->rest.f.precision = ANIM_GLIDE1_THICKNESS_PRECISION;

	// Glide 2

	o = &as->opt[ANIM_SCREEN_OPTION_GLIDE2_AWAY_POS];
	o->name = "glide2_away_position";
	o->group = N_("Glide");
	o->subGroup = N_("Glide 2");
	o->advanced = False;
	o->shortDesc = N_("Away Position");
	o->longDesc = N_("Closeness of window to camera at the end of the "
					 "animation "
					 "(1.0: Close to camera, -2.0: Away from camera).");
	o->displayHints = "";
	o->type = CompOptionTypeFloat;
	o->value.f = ANIM_GLIDE2_AWAY_POS_DEFAULT;
	o->rest.f.min = ANIM_GLIDE2_AWAY_POS_MIN;
	o->rest.f.max = ANIM_GLIDE2_AWAY_POS_MAX;
	o->rest.f.precision = ANIM_GLIDE2_AWAY_POS_PRECISION;

	o = &as->opt[ANIM_SCREEN_OPTION_GLIDE2_AWAY_ANGLE];
	o->name = "glide2_away_angle";
	o->group = N_("Glide");
	o->subGroup = N_("Glide 2");
	o->advanced = False;
	o->shortDesc = N_("Away Angle");
	o->longDesc = N_("Angle of window at the end of the animation.");
	o->displayHints = "";
	o->type = CompOptionTypeFloat;
	o->value.f = ANIM_GLIDE2_AWAY_ANGLE_DEFAULT;
	o->rest.f.min = ANIM_GLIDE2_AWAY_ANGLE_MIN;
	o->rest.f.max = ANIM_GLIDE2_AWAY_ANGLE_MAX;
	o->rest.f.precision = ANIM_GLIDE2_AWAY_ANGLE_PRECISION;

	o = &as->opt[ANIM_SCREEN_OPTION_GLIDE2_THICKNESS];
	o->name = "glide2_thickness";
	o->group = N_("Glide");
	o->subGroup = N_("Glide 2");
	o->advanced = False;
	o->shortDesc = N_("Thickness");
	o->longDesc = N_("Window thickness in pixels. Setting this to larger "
					 "than 0 will disable shadow, blur, and reflection "
					 "during the animation.");
	o->displayHints = "";
	o->type = CompOptionTypeFloat;
	o->value.f = ANIM_GLIDE2_THICKNESS_DEFAULT;
	o->rest.f.min = ANIM_GLIDE2_THICKNESS_MIN;
	o->rest.f.max = ANIM_GLIDE2_THICKNESS_MAX;
	o->rest.f.precision = ANIM_GLIDE2_THICKNESS_PRECISION;
}

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

		*count = NUM_OPTIONS(as);
		return as->opt;
	}
	else
	{
		AnimScreen *as = calloc(1, sizeof(AnimScreen));

		animScreenInitOptions(as);
		*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;
}

static 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 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)
	{
		fprintf(stderr, "%s: Not enough memory at line %d!\n",
				__FILE__, __LINE__);
		return 0;
	}
	model->magicLampWaveCount = 0;
	model->magicLampWaves = NULL;

	model->gridWidth = gridWidth;
	model->gridHeight = gridHeight;
	model->numObjects = gridWidth * gridHeight;
	model->objects = calloc(1, sizeof(Object) * model->numObjects);
	if (!model->objects)
	{
		fprintf(stderr, "%s: Not enough memory at line %d!\n",
				__FILE__, __LINE__);
		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->remainderSteps = 0;

	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);

	modelCalcBounds(model);

	return model;
}

static Bool
animEnsureModel(CompWindow * w,
				WindowEvent forWindowEvent, AnimEffect forAnimEffect)
{
	ANIM_WINDOW(w);
	ANIM_SCREEN(w->screen);

	int gridWidth = 2;
	int gridHeight = 2;

	if (animEffectProperties[forAnimEffect].initGridFunc)
		animEffectProperties[forAnimEffect].initGridFunc(as,
														 forWindowEvent,
														 &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))
	{
		if (aw->model)
		{
			if (aw->model->magicLampWaves)
				free(aw->model->magicLampWaves);
			free(aw->model->objects);
			free(aw->model);
		}
		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;

	float finalDistFac;
	float finalRotAng;
	float thickness;

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

	return (thickness > 1e-5); // glide is 3D if thickness > 0
}

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

	IPCS_SetBool(IPCS_OBJECT(w), aw->animatedAtom, FALSE);

	if (resetAnimation)
	{
		// if 3d polygon fx
		if (playingPolygonEffect(as, aw))
		{
			IPCS_SetBoolN(IPCS_OBJECT(w), "DISABLE_BLUR", FALSE);
			IPCS_SetBoolN(IPCS_OBJECT(w), "DISABLE_REFLECTION", FALSE);
		}
		if (as->ppDisabling == PostprocessDisablingWindow)
			IPCS_SetBoolN(IPCS_OBJECT(w), "DISABLE_BLUR", FALSE);
		if (!(as->animInProgress)
			&& as->ppDisabling == PostprocessDisablingScreen)
			IPCS_SetBoolN(IPCS_OBJECT(w->screen), "DISABLE_BLUR", FALSE);

		aw->curWindowEvent = WindowEventNone;
		aw->curAnimEffect = AnimEffectNone;
		aw->animOverrideProgressDir = 0;

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

	aw->state = aw->newState;

	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++)
		{
			free(aw->ps[i].particles);
			if (aw->ps[i].tex)
				glDeleteTextures(1, &aw->ps[i].tex);
		}
		free(aw->ps);
		aw->ps = NULL;
		aw->numPs = 0;
	}

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

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

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

	ANIM_SCREEN(s);

	if (as->animInProgress)
	{
		AnimWindow *aw;
		BoxRec box;
		Point topLeft, bottomRight;

		if (as->ppDisabling == PostprocessDisablingScreen)
			IPCS_SetBoolN(IPCS_OBJECT(s), "DISABLE_BLUR", TRUE);
		else
			IPCS_SetBoolN(IPCS_OBJECT(s), "DISABLE_BLUR", FALSE);

		as->animInProgress = 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);
						as->animInProgress = TRUE;
					}
				}
			}

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

					aw->deceleratingMotion =
							animEffectProperties[aw->curAnimEffect].
							animStepPolygonFunc ==
							polygonsDeceleratingAnimStepPolygon;

					if (playingPolygonEffect(as, aw))
					{
						// Allocate polygon set if null
						if (!aw->polygonSet)
						{
							//printf("calloc aw->polygonSet\n");
							aw->polygonSet = calloc(1, sizeof(PolygonSet));
						}
						if (!aw->polygonSet)
						{
							fprintf(stderr,
									"%s: Not enough memory at line %d!\n",
									__FILE__, __LINE__);
							return;
						}
						aw->polygonSet->allFadeDuration = -1.0f;
					}
				}

				//aw->drawCount = 0;
				//printf("aw->drawCount init: %d\n", aw->drawCount);

				if (as->ppDisabling == PostprocessDisablingWindow)
					IPCS_SetBoolN(IPCS_OBJECT(w), "DISABLE_BLUR", TRUE);
				else
					IPCS_SetBoolN(IPCS_OBJECT(w), "DISABLE_BLUR", FALSE);

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

					IPCS_SetBoolN(IPCS_OBJECT(w), "DISABLE_BLUR", TRUE);
					IPCS_SetBoolN(IPCS_OBJECT(w), "DISABLE_REFLECTION", TRUE);
				}
				if (aw->model)
				{
					topLeft = aw->model->topLeft;
					bottomRight = aw->model->bottomRight;

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

					if (aw->model->winWidth != WIN_W(w) ||
						aw->model->winHeight != WIN_H(w))
					{
						// model needs update

						// keep this value
						float remainderSteps = aw->model->remainderSteps;

						// re-create model
						animEnsureModel
								(w, aw->curWindowEvent, aw->curAnimEffect);
						if (aw->model == 0)
							continue;	// skip this window
						aw->model->remainderSteps = remainderSteps;
					}
					// Call fx step func.
					if (animEffectProperties[aw->curAnimEffect].animStepFunc)
					{
						animEffectProperties[aw->
											 curAnimEffect].
								animStepFunc(s, w, msSinceLastPaint);
					}
					if (aw->animRemainingTime <= 0)
					{
						// Animation done
						postAnimationCleanup(w, TRUE);
					}

					if (!(s->damageMask & COMP_SCREEN_DAMAGE_ALL_MASK))
					{
						if (aw->animRemainingTime > 0)
						{
							if (aw->model->topLeft.x < topLeft.x)
								topLeft.x = aw->model->topLeft.x;
							if (aw->model->topLeft.y < topLeft.y)
								topLeft.y = aw->model->topLeft.y;
							if (aw->model->bottomRight.x > bottomRight.x)
								bottomRight.x = aw->model->bottomRight.x;
							if (aw->model->bottomRight.y > bottomRight.y)
								bottomRight.y = aw->model->bottomRight.y;
						}
						else
							addWindowDamage(w);

						box.x1 = topLeft.x;
						box.y1 = topLeft.y;
						box.x2 = bottomRight.x + 0.5f;
						box.y2 = bottomRight.y + 0.5f;

						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);
					}
				}
				as->animInProgress |= (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;
			}
		}
	}

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

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

	if (as->animInProgress)
	{
		damageScreen(s);
	}
	if (!(as->animInProgress) &&
		as->ppDisabling == PostprocessDisablingScreen)
		IPCS_SetBoolN(IPCS_OBJECT(s), "DISABLE_BLUR", FALSE);

	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)
	{
		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;
			}
		}

		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);
			   } */

			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)
								offsetY =
										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 WindowPaintAttrib * attrib, unsigned int mask)
{
	ANIM_WINDOW(w);
	ANIM_SCREEN(w->screen);

	if (aw->animRemainingTime > 0)	// if animation in progress, store texture
	{
		//printf("%X animDrawWindowTexture, texture: %X\n",
		//     (unsigned)aw, (unsigned)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);
}


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

	if (aw->animRemainingTime > 0)
	{
		//printf("animDrawWindowGeometry: %X: coords: %d, %d, %f\n",
		//       (unsigned)aw, WIN_X (w), WIN_Y (w), aw->animRemainingTime);
		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);
		}
	}
	else
	{
		ANIM_SCREEN(w->screen);

		UNWRAP(as, w->screen, drawWindowGeometry);
		(*w->screen->drawWindowGeometry) (w);
		WRAP(as, w->screen, drawWindowGeometry, animDrawWindowGeometry);
	}
}

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

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

	if (aw->animRemainingTime > 0)
	{
		//printf("animPaintWindow: %X: coords: %d, %d\n",
		//       (unsigned)aw, WIN_X (w), WIN_X (w));

		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;

		if (mask & PAINT_WINDOW_SOLID_MASK)
			return FALSE;

		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, aw, &wAttrib);

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

		UNWRAP(as, w->screen, paintWindow);
		status = (*w->screen->paintWindow) (w, &wAttrib, 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, 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 void animHandleEvent(CompDisplay * d, XEvent * event)
{
	Window activeWindow = 0;
	CompWindow *w;

	ANIM_DISPLAY(d);

	switch (event->type)
	{
	case PropertyNotify:
		if (event->xproperty.atom == d->winActiveAtom)
			activeWindow = d->activeWindow;
		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);
				if (w->shaded)
				{
					// SHADE event!

					//printf("SHADE event! %X\n", (unsigned)aw);

					aw->nowShaded = TRUE;

					if (as->shadeEffect && (as->shadeWMask & w->type))
					{
						IPCS_SetBool(IPCS_OBJECT(w), aw->animatedAtom, TRUE);
						Bool startingNew = TRUE;

						if (aw->curWindowEvent != WindowEventNone)
						{
							if (aw->curWindowEvent != WindowEventUnshade)
								postAnimationCleanup(w, TRUE);
							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;
							}
						}

						as->animInProgress = TRUE;
						aw->curWindowEvent = WindowEventShade;

						if (startingNew)
						{
							if (as->shadeEffect == AnimEffectRandom
								|| as->
								opt[ANIM_SCREEN_OPTION_ALL_RANDOM].value.b)
								aw->curAnimEffect =	// choose a random effect
										as->shadeRandomEffects[(int)
															   (as->
																nShadeRandomEffects
																*
																(double)rand()
																/ RAND_MAX)];
							else
								aw->curAnimEffect = as->shadeEffect;

							aw->animTotalTime =
									as->
									opt
									[ANIM_SCREEN_OPTION_SHADE_DURATION].
									value.f * 1000;
							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;

						if (!animEnsureModel
							(w, WindowEventShade, aw->curAnimEffect))
						{
							postAnimationCleanup(w, TRUE);
						}

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

						addWindowDamage(w);
					}
				}
				else if (w->minimized
						 && as->minimizeEffect
						 && (as->minimizeWMask & w->type))
				{
					// MINIMIZE event!

					//printf("MINIMIZE event! %X\n", (unsigned)aw);

					IPCS_SetBool(IPCS_OBJECT(w), aw->animatedAtom, TRUE);
					Bool startingNew = TRUE;

					if (aw->curWindowEvent != WindowEventNone)
					{
						if (aw->curWindowEvent != WindowEventUnminimize)
							postAnimationCleanup(w, TRUE);
						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;
						}
					}

					aw->newState = IconicState;
					as->animInProgress = TRUE;
					aw->curWindowEvent = WindowEventMinimize;

					if (startingNew)
					{
						if (as->minimizeEffect ==
							AnimEffectRandom
							|| as->opt[ANIM_SCREEN_OPTION_ALL_RANDOM].value.b)
							aw->curAnimEffect =	// choose a random effect
									as->minimizeRandomEffects[(int)
															  (as->
															   nMinimizeRandomEffects
															   *
															   (double)rand()
															   / RAND_MAX)];
						else
							aw->curAnimEffect = as->minimizeEffect;

						aw->animTotalTime =
								as->
								opt
								[ANIM_SCREEN_OPTION_MINIMIZE_DURATION].
								value.f * 1000;
						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;

					if (!animEnsureModel
						(w, WindowEventMinimize, aw->curAnimEffect))
					{
						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;
						}
						if ((aw->curAnimEffect ==
							 AnimEffectZoom
							 || aw->
							 curAnimEffect ==
							 AnimEffectSidekick)
							&&
							(as->zoomFC == ZoomFromCenterOn ||
							 as->zoomFC == ZoomFromCenterMin))
						{
							aw->icon.x =
									WIN_X(w) +
									WIN_W(w) / 2 - aw->icon.width / 2;
							aw->icon.y =
									WIN_Y(w) +
									WIN_H(w) / 2 - aw->icon.height / 2;
						}

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

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

				AnimEffect windowsCloseEffect = AnimEffectNone;
				int whichClose = 1;	// either 1 or 2

				if (as->close1Effect && (as->close1WMask & w->type))
					windowsCloseEffect = as->close1Effect;
				else if (as->close2Effect && (as->close2WMask & w->type))
				{
					windowsCloseEffect = as->close2Effect;
					whichClose = 2;
				}
				// CLOSE event!

				if (windowsCloseEffect)
				{
					IPCS_SetBool(IPCS_OBJECT(w), aw->animatedAtom, TRUE);
					int tmpSteps = 0;

					//printf("CLOSE event! %X\n", (unsigned)aw);

					Bool startingNew = TRUE;

					if (aw->animRemainingTime > 0 &&
						aw->curWindowEvent != WindowEventCreate)
					{
						tmpSteps = aw->animRemainingTime;
						aw->animRemainingTime = 0;
					}
					if (aw->curWindowEvent != WindowEventNone)
					{
						if (aw->curWindowEvent == WindowEventCreate)
						{
							// 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
						{
							postAnimationCleanup(w, TRUE);
						}
					}

					aw->state = NormalState;
					aw->newState = WithdrawnState;
					as->animInProgress = TRUE;
					aw->curWindowEvent = WindowEventClose;

					if (startingNew)
					{
						if (windowsCloseEffect ==
							AnimEffectRandom
							|| as->opt[ANIM_SCREEN_OPTION_ALL_RANDOM].value.b)
						{
							if (whichClose == 1)
								aw->curAnimEffect =	// choose a random effect
										as->close1RandomEffects[(int)
																(as->
																 nClose1RandomEffects
																 *
																 (double)
																 rand() /
																 RAND_MAX)];
							else
								aw->curAnimEffect =	// choose a random effect
										as->close2RandomEffects[(int)
																(as->
																 nClose2RandomEffects
																 *
																 (double)
																 rand() /
																 RAND_MAX)];
						}
						else
							aw->curAnimEffect = windowsCloseEffect;

						aw->animTotalTime =
								as->opt[whichClose ==
										1 ?
										ANIM_SCREEN_OPTION_CLOSE1_DURATION
										:
										ANIM_SCREEN_OPTION_CLOSE2_DURATION].
								value.f * 1000;
						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;

					if (!animEnsureModel
						(w, WindowEventClose, aw->curAnimEffect))
					{
						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 == AnimEffectMagicLamp1 &&
							aw->icon.width <
							as->
							opt
							[ANIM_SCREEN_OPTION_MAGIC_LAMP1_CREATE_START_WIDTH].
							value.i)
						{
							aw->icon.width =
									as->
									opt
									[ANIM_SCREEN_OPTION_MAGIC_LAMP1_CREATE_START_WIDTH].
									value.i;
						}
						else if (aw->curAnimEffect == AnimEffectMagicLamp2 &&
								 aw->icon.width <
								 as->
								 opt
								 [ANIM_SCREEN_OPTION_MAGIC_LAMP2_CREATE_START_WIDTH].
								 value.i)
						{
							aw->icon.width =
									as->
									opt
									[ANIM_SCREEN_OPTION_MAGIC_LAMP2_CREATE_START_WIDTH].
									value.i;
						}

						else if (aw->curAnimEffect == AnimEffectMagicLamp3 &&
								 aw->icon.width <
								 as->
								 opt
								 [ANIM_SCREEN_OPTION_MAGIC_LAMP3_CREATE_START_WIDTH].
								 value.i)
						{
							aw->icon.width =
									as->
									opt
									[ANIM_SCREEN_OPTION_MAGIC_LAMP3_CREATE_START_WIDTH].
									value.i;
						}

						if ((aw->curAnimEffect ==
							 AnimEffectZoom
							 || aw->
							 curAnimEffect ==
							 AnimEffectSidekick)
							&&
							(as->zoomFC == ZoomFromCenterOn ||
							 as->zoomFC == ZoomFromCenterCreate))
						{
							aw->icon.x =
									WIN_X(w) +
									WIN_W(w) / 2 - aw->icon.width / 2;
							aw->icon.y =
									WIN_Y(w) +
									WIN_H(w) / 2 - aw->icon.height / 2;
						}

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

						addWindowDamage(w);
					}
				}
				else if ((as->create1Effect
						  && (as->create1WMask & w->type))
						 || (as->create2Effect
							 && (as->create2WMask & w->type)))
				{
					// stop the current animation and prevent it from rewinding

					if (aw->animRemainingTime > 0 &&
						aw->curWindowEvent != WindowEventCreate)
					{
						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 create effect the next time it's "created"

					aw->state = NormalState;
					aw->newState = WithdrawnState;
					as->animInProgress = TRUE;
					aw->curWindowEvent = WindowEventClose;

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

					addWindowDamage(w);
				}
			}
		}
		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)
		{
			if (d->activeWindow != activeWindow)
			{
				w = findWindowAtDisplay(d, d->activeWindow);
				if (w)
				{
					ANIM_WINDOW(w);
					ANIM_SCREEN(w->screen);

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

					if ((as->focusWMask & w->type) && as->focusEffect &&
						// 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
						&& animEnsureModel(w,
										   WindowEventFocus, as->focusEffect))
					{
						// FOCUS event!

						//printf("FOCUS event! %X\n", (unsigned)aw);

						IPCS_SetBool(IPCS_OBJECT(w), aw->animatedAtom, TRUE);
						if (aw->curWindowEvent != WindowEventNone)
						{
							postAnimationCleanup(w, TRUE);
						}

						as->animInProgress = TRUE;
						aw->curWindowEvent = WindowEventFocus;
						aw->curAnimEffect = as->focusEffect;
						aw->animTotalTime =
								as->
								opt
								[ANIM_SCREEN_OPTION_FOCUS_DURATION].
								value.f * 1000;
						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;

						damageScreen(w->screen);
					}
				}
			}
		}

	default:
		break;
	}
}

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

	ANIM_SCREEN(w->screen);

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

		if (aw->state == IconicState)
		{
			if (!w->invisible && as->unminimizeEffect
				&& (as->unminimizeWMask & w->type))
			{
				// UNMINIMIZE event!

				//printf("UNMINIMIZE event! %X\n", (unsigned)aw);

				IPCS_SetBool(IPCS_OBJECT(w), aw->animatedAtom, TRUE);
				Bool startingNew = TRUE;

				if (aw->curWindowEvent != WindowEventNone)
				{
					if (aw->curWindowEvent != WindowEventMinimize)
						postAnimationCleanup(w, TRUE);
					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;
					}
				}
				as->animInProgress = TRUE;
				aw->curWindowEvent = WindowEventUnminimize;

				if (startingNew)
				{
					if (as->unminimizeEffect ==
						AnimEffectRandom
						|| as->opt[ANIM_SCREEN_OPTION_ALL_RANDOM].value.b)
						aw->curAnimEffect =	// choose a random effect
								as->unminimizeRandomEffects[(int)
															(as->
															 nUnminimizeRandomEffects
															 *
															 (double)rand() /
															 RAND_MAX)];
					else
						aw->curAnimEffect = as->unminimizeEffect;

					aw->animTotalTime =
							as->
							opt
							[ANIM_SCREEN_OPTION_UNMINIMIZE_DURATION].
							value.f * 1000;
					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;

				if (animEnsureModel
					(w, WindowEventUnminimize, aw->curAnimEffect))
				{
					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;
					}
					if ((aw->curAnimEffect ==
						 AnimEffectZoom
						 || aw->curAnimEffect ==
						 AnimEffectSidekick)
						&&
						(as->zoomFC == ZoomFromCenterOn ||
						 as->zoomFC == ZoomFromCenterMin))
					{
						aw->icon.x =
								WIN_X(w) + WIN_W(w) / 2 - aw->icon.width / 2;
						aw->icon.y =
								WIN_Y(w) + WIN_H(w) / 2 - aw->icon.height / 2;
					}
					addWindowDamage(w);
				}
				else
					postAnimationCleanup(w, TRUE);
			}
		}
		else if (aw->nowShaded)
		{
			// UNSHADE event!
			//printf("UNSHADE event! %X\n", (unsigned)aw);

			IPCS_SetBool(IPCS_OBJECT(w), aw->animatedAtom, TRUE);
			aw->nowShaded = FALSE;

			if (as->unshadeEffect && (as->unshadeWMask & w->type))
			{
				Bool startingNew = TRUE;

				if (aw->curWindowEvent != WindowEventNone)
				{
					if (aw->curWindowEvent != WindowEventShade)
						postAnimationCleanup(w, TRUE);
					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;
					}
				}
				as->animInProgress = TRUE;
				aw->curWindowEvent = WindowEventUnshade;

				if (startingNew)
				{
					if (as->unshadeEffect ==
						AnimEffectRandom
						|| as->opt[ANIM_SCREEN_OPTION_ALL_RANDOM].value.b)
						aw->curAnimEffect =	// choose a random effect
								as->unshadeRandomEffects[(int)
														 (as->
														  nUnshadeRandomEffects
														  * (double)rand() /
														  RAND_MAX)];
					else
						aw->curAnimEffect = as->unshadeEffect;

					aw->animTotalTime =
							as->
							opt
							[ANIM_SCREEN_OPTION_UNSHADE_DURATION].value.f *
							1000;
					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;

				if (animEnsureModel(w, WindowEventUnshade, aw->curAnimEffect))
					addWindowDamage(w);
				else
					postAnimationCleanup(w, TRUE);
			}
		}
		else if (aw->state != NormalState && !w->invisible)
		{
			AnimEffect windowsCreateEffect = AnimEffectNone;

			int whichCreate = 1;	// either 1 or 2

			if (as->create1Effect && (as->create1WMask & w->type))
				windowsCreateEffect = as->create1Effect;
			else if (as->create2Effect && (as->create2WMask & w->type))
			{
				windowsCreateEffect = as->create2Effect;
				whichCreate = 2;
			}

			if (windowsCreateEffect &&
				getMousePointerXY(w->screen, &aw->icon.x, &aw->icon.y))
			{
				// CREATE event!

				//printf("CREATE event! %X\n", (unsigned)aw);

				IPCS_SetBool(IPCS_OBJECT(w), aw->animatedAtom, TRUE);
				Bool startingNew = TRUE;

				if (aw->curWindowEvent != WindowEventNone)
				{
					if (aw->curWindowEvent != WindowEventClose)
						postAnimationCleanup(w, TRUE);
					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;
					}
				}
				as->animInProgress = TRUE;
				aw->curWindowEvent = WindowEventCreate;

				if (startingNew)
				{
					if (windowsCreateEffect ==
						AnimEffectRandom
						|| as->opt[ANIM_SCREEN_OPTION_ALL_RANDOM].value.b)
					{
						if (whichCreate == 1)
							aw->curAnimEffect =	// choose a random effect
									as->create1RandomEffects[(int)
															 (as->
															  nCreate1RandomEffects
															  *
															  (double)rand() /
															  RAND_MAX)];
						else
							aw->curAnimEffect =	// choose a random effect
									as->create2RandomEffects[(int)
															 (as->
															  nCreate2RandomEffects
															  *
															  (double)rand() /
															  RAND_MAX)];
					}
					else
						aw->curAnimEffect = windowsCreateEffect;

					aw->animTotalTime =
							as->opt[whichCreate == 1 ?
									ANIM_SCREEN_OPTION_CREATE1_DURATION
									:
									ANIM_SCREEN_OPTION_CREATE2_DURATION].
							value.f * 1000;
					aw->animRemainingTime = aw->animTotalTime;
				}

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

				if (aw->curAnimEffect == AnimEffectMagicLamp1 &&
					aw->icon.width <
					as->
					opt[ANIM_SCREEN_OPTION_MAGIC_LAMP1_CREATE_START_WIDTH].
					value.i)
				{
					aw->icon.width =
							as->
							opt
							[ANIM_SCREEN_OPTION_MAGIC_LAMP1_CREATE_START_WIDTH].
							value.i;
				}
				else if (aw->curAnimEffect == AnimEffectMagicLamp2 &&
						 aw->icon.width <
						 as->
						 opt
						 [ANIM_SCREEN_OPTION_MAGIC_LAMP2_CREATE_START_WIDTH].
						 value.i)
				{
					aw->icon.width =
							as->
							opt
							[ANIM_SCREEN_OPTION_MAGIC_LAMP2_CREATE_START_WIDTH].
							value.i;
				}
				else if (aw->curAnimEffect == AnimEffectMagicLamp3 &&
						 aw->icon.width <
						 as->
						 opt
						 [ANIM_SCREEN_OPTION_MAGIC_LAMP3_CREATE_START_WIDTH].
						 value.i)
				{
					aw->icon.width =
							as->
							opt
							[ANIM_SCREEN_OPTION_MAGIC_LAMP3_CREATE_START_WIDTH].
							value.i;
				}
				aw->icon.x -= aw->icon.width / 2;
				aw->icon.y -= aw->icon.height / 2;

				if ((aw->curAnimEffect == AnimEffectZoom ||
					 aw->curAnimEffect == AnimEffectSidekick) &&
					(as->zoomFC == ZoomFromCenterOn ||
					 as->zoomFC == ZoomFromCenterCreate))
				{
					aw->icon.x = WIN_X(w) + WIN_W(w) / 2 - aw->icon.width / 2;
					aw->icon.y =
							WIN_Y(w) + WIN_H(w) / 2 - aw->icon.height / 2;
				}
				aw->state = IconicState;	// we're doing this as a hack, it may not be necessary

				// 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, WindowEventCreate, aw->curAnimEffect))
					addWindowDamage(w);
				else
					postAnimationCleanup(w, 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, Bool preview)
{
	ANIM_SCREEN(w->screen);
	ANIM_WINDOW(w);

	if (aw->polygonSet && !aw->animInitialized)
	{
		// to refresh polygon coords
		freePolygonSet(aw);
	}

	if (!preview && (dx || dy || dwidth || dheight))
	{
		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, preview);
	WRAP(as, w->screen, windowResizeNotify, animWindowResizeNotify);
}

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

	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)
		{
			as->animInProgress = FALSE;
			for (w2 = w->screen->windows; w2; w2 = w2->next)
			{
				AnimWindow *aw2;

				aw2 = GET_ANIM_WINDOW(w2, as);
				if (aw2->animRemainingTime > 0)
				{
					as->animInProgress = TRUE;
					break;
				}
			}
		}
		postAnimationCleanup(w, TRUE);
	}
	if (aw->model)
	{
		modelInitObjects(aw->model, WIN_X(w), WIN_Y(w), WIN_W(w),
						 WIN_H(w));
	}

	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
animPaintScreen(CompScreen * s,
				const ScreenPaintAttrib * sAttrib,
				Region region, int 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;
	}
	UNWRAP(as, s, paintScreen);
	status = (*s->paintScreen) (s, sAttrib, region, output, mask);
	WRAP(as, s, paintScreen, animPaintScreen);

	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->wmHintsAtom = XInternAtom(d->display, "WM_HINTS", FALSE);
	ad->winIconGeometryAtom =
			XInternAtom(d->display, "_NET_WM_ICON_GEOMETRY", 0);

	WRAP(ad, d, handleEvent, animHandleEvent);

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

	return TRUE;
}

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

	freeScreenPrivateIndex(d, ad->screenPrivateIndex);

	UNWRAP(ad, d, handleEvent);

	free(ad);
}

static Bool animInitScreen(CompPlugin * p, CompScreen * s)
{
	AnimScreen *as;

	ANIM_DISPLAY(s->display);

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

	as->windowPrivateIndex = allocateWindowPrivateIndex(s);
	if (as->windowPrivateIndex < 0)
	{
		free(as);
		return FALSE;
	}
	as->animInProgress = FALSE;
	as->minimizeEffect = ANIM_MINIMIZE_DEFAULT;
	as->unminimizeEffect = ANIM_UNMINIMIZE_DEFAULT;
	as->create1Effect = ANIM_CREATE1_DEFAULT;
	as->create2Effect = ANIM_CREATE2_DEFAULT;
	as->close1Effect = ANIM_CLOSE1_DEFAULT;
	as->close2Effect = ANIM_CLOSE2_DEFAULT;
	as->focusEffect = ANIM_FOCUS_DEFAULT;
	as->shadeEffect = ANIM_SHADE_DEFAULT;
	as->unshadeEffect = ANIM_UNSHADE_DEFAULT;

	as->zoomFC = ANIM_ZOOM_FROM_CENTER_DEFAULT;

	as->ppDisabling = ANIM_DISABLE_PP_FX_DEFAULT;

	animScreenInitOptions(as);

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

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

	return TRUE;
}

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

	freeWindowPrivateIndex(s, as->windowPrivateIndex);

	free(as->opt[ANIM_SCREEN_OPTION_MINIMIZE_EFFECT].value.s);
	free(as->opt[ANIM_SCREEN_OPTION_UNMINIMIZE_EFFECT].value.s);
	free(as->opt[ANIM_SCREEN_OPTION_CREATE1_EFFECT].value.s);
	free(as->opt[ANIM_SCREEN_OPTION_CREATE2_EFFECT].value.s);
	free(as->opt[ANIM_SCREEN_OPTION_CLOSE1_EFFECT].value.s);
	free(as->opt[ANIM_SCREEN_OPTION_CLOSE2_EFFECT].value.s);
	free(as->opt[ANIM_SCREEN_OPTION_FOCUS_EFFECT].value.s);
	free(as->opt[ANIM_SCREEN_OPTION_SHADE_EFFECT].value.s);
	free(as->opt[ANIM_SCREEN_OPTION_UNSHADE_EFFECT].value.s);

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

	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->animatedAtom = IPCS_GetAtom(IPCS_OBJECT(w), IPCS_BOOL,
									"IS_ANIMATED", TRUE);
	IPCS_SetBool(IPCS_OBJECT(w), aw->animatedAtom, FALSE);
	aw->model = 0;
	aw->state = w->state;
	aw->animRemainingTime = 0;
	aw->animInitialized = FALSE;
	aw->curAnimEffect = AnimEffectNone;
	aw->curWindowEvent = WindowEventNone;
	aw->animOverrideProgressDir = 0;
	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;

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

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

	return TRUE;
}

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

	postAnimationCleanup(w, FALSE);

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

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

	free(aw);
}

static Bool animInit(CompPlugin * p)
{
	displayPrivateIndex = allocateDisplayPrivateIndex();
	if (displayPrivateIndex < 0)
		return FALSE;

	animEffectPropertiesTmp = animEffectProperties;
	return TRUE;
}

static void animFini(CompPlugin * p)
{
	if (displayPrivateIndex >= 0)
		freeDisplayPrivateIndex(displayPrivateIndex);
}

CompPluginDep animDeps[] = {
	{CompPluginRuleAfter, "decoration"}
};

CompPluginVTable animVTable = {
	"animation",
	N_("Animations"),
	N_("Use various animations as window effects"),
	animInit,
	animFini,
	animInitDisplay,
	animFiniDisplay,
	animInitScreen,
	animFiniScreen,
	animInitWindow,
	animFiniWindow,
	0,
	0,
	animGetScreenOptions,
	animSetScreenOption,
	animDeps,
	sizeof(animDeps) / sizeof(animDeps[0]),
	0,
	0,
	BERYL_ABI_INFO,
	"beryl-plugins",
	"effects",
	0,
	0,
	True,
};

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


syntax highlighted by Code2HTML, v. 0.9.1