/* ripple.h

   This file contains header definitions for the ripple effect.
*/

#ifndef INCLUDED_RIPPLES_H
#define INCLUDED_RIPPLES_H

#include "allegro.h"
#include <stdlib.h> /* For the destroy_ripples() ... er, function */


/* The dimensions of a ripple tile. It is available in binary shift form as
   well as numerical form.

   x >> RIPPLE_SH == x / RIPPLE_D - only for positive numbers
   x << RIPPLE_SH == x * RIPPLE_D

   Hence RIPPLE_D must be a power of two. This is done to make certain
   optimisations possible when drawing.
*/
#define RIPPLE_SH 3 /* 16x16 tiles */
#define RIPPLE_D (1 << RIPPLE_SH)

#define RIPPLE_FRAME_SH 3 /* One propagation every eight frames */
#define RIPPLE_FRAMES (1 << RIPPLE_FRAME_SH)

/* A ripple point. This holds the vertical position of one point on the
   liquid. We remember the previous state as well as the current state, and
   use the difference between the two states to work out the velocity.
*/
typedef int ripple_p;


/* A ripple tile, defined as two grids of ripple points. They indicate the
   previous and current states of the ripples.
*/
typedef struct RIPPLES {
 ripple_p prev[RIPPLE_D * RIPPLE_D];
 ripple_p curr[RIPPLE_D * RIPPLE_D];
 int frame; /* The frame in which this tile should be propagated. */
} RIPPLES;


/* A disturbance in a ripple tile. Where a disturbance overlaps multiple
   ripple tiles, separate disturbances should be created. This is a linked
   list. The disturbances will actually be applied in reverse order, but it
   makes no difference to the outcome.
*/
typedef struct R_DISTURBANCE {
 struct R_DISTURBANCE *next;
 RIPPLES *ripples; /* Which tile is disturbed. */
 float x, y; /* Coordinates in this tile, ranging from 0 to 1. */
 float r; /* Radius. */
 float h; /* Height/Depth. */
} R_DISTURBANCE;


/* A ripple info block. Keep one of these for each tile map.
   'swapped' is a flag which, when true, reverses the functions of the prev[]
   and curr[] arrays in all ripple tiles. 'dist' contains a list of
   disturbances.
*/
typedef struct RIPPLE_INFO {
 int frame; /* The current ripple frame. */
 int swapped;
 R_DISTURBANCE *dist;
} RIPPLE_INFO;


/* create_ripple_info() creates a ripple info block and initialises it.
   create_ripples() does the same for a ripple tile. No prizes for guessing
   what the destroy_*() functions do.

   create_ripples() contains a static frame counter, used to allocate each
   new ripple tile a different propagation frame.

   destroy_ripple_info() must also destroy the list of disturbances. It uses
   destroy_disturbance() to do this, but take care, since they are stored in
   a linked list.
*/
RIPPLE_INFO *create_ripple_info();
void destroy_ripple_info(RIPPLE_INFO *ripple_info);

RIPPLES *create_ripples();
void destroy_ripples(RIPPLES *r);



/* propagate_ripples() should be called once for every tile every frame. It
   will only process tiles whose 'frame' fields match up with the 'frame'
   field in ripple_info. Thus the workload is spread over several frames.
   Despite this rota system, all tiles will appear to change at the same
   time, owing to the nature of the algorithm.

   Pass pointers to the neighbouring ripple tiles so that waves can spread
   across the join. If there is no liquid to one side, then pass null for the
   pointer on that side. Waves will then be reflected off this side.

   This function will take the amount the ripples moved from the previous
   state to the current state as the velocity. It will then calculate the
   average position of the neighbouring four points, subtract the position of
   the central point, halve this value, and add it to the velocity; in other
   words, the point is attracted towards the positions of the neighbouring
   points. Finally we damp the waves (as if they weren't already wet! BOOM,
   BOOM. :-P ), apply a small attraction towards the centre, and store the
   new position (current position plus velocity) in the 'prev' array. This
   stuff can all be combined into only one or two equations, and is very
   fast.

   Since we are only reading the positions of neighbouring tiles, we only
   ever access their 'curr' arrays. Our algorithm does not change the 'curr'
   array, so the order in which the tiles are propagated does not matter.

   During each frame, after all the calls to this function, you should call
   finalise_ripples() once.
*/
void propagate_ripples(RIPPLE_INFO *ripple_info,
                       RIPPLES *ripples,
                       RIPPLES *rip_l,
                       RIPPLES *rip_r,
                       RIPPLES *rip_t,
                       RIPPLES *rip_b);


/* After all the calls to propagate_ripples(), this function must be called
   once. First, it increments the 'frame' field in ripple_info. If it reaches
   RIPPLE_FRAMES, it is reset to zero and the following steps are taken:

   1. Invert the 'swapped' flag. This will have the effect of making
      the new ripple frame visible.

   2. Apply any disturbances that have been listed (see below).
*/
void finalise_ripples(RIPPLE_INFO *ripple_info);


/* create_disturbance() creates a disturbance in the ripples by adding it to
   the 'dist' field in ripple_info. The disturbance takes the form of a
   circle, radius 'r', within which the water will be displaced. 'x' and 'y'
   are measured relative to the tile, and are in the range 0 to 1. At the
   edge of the circle, the water will not be touched; in the centre, it will
   move up or down by h. If h is positive it moves up; if h is negative it
   moves down.

   This function does not adjust the water; it simply adds an entry to the
   linked list of disturbances. Alternatively, if a disturbance of similar
   position and size is already listed for the same tile, this function
   merges them. finalise_ripples() will use apply_disturbance() to apply the
   disturbances when the time is right, ensuring that no disturbances are
   applied when some tiles, but not all, have been propagated.
*/
void create_disturbance(RIPPLE_INFO *ripple_info,
                        RIPPLES *ripples,
                        float x, float y, float r, float h);


/* apply_disturbance() applies the specified disturbance to its ripple tile.
   This will only be called by finalise_ripples(). It is also passed the
   current ripple point array, which will be ripples->prev[] if
   ripple_info->swapped is set.

   The disturbance will be applied using a parabolic function. This has the
   property that it may be calculated in the two coordinate axes
   independently, and the results summed, producing a circular pattern. It
   can also be optimised using discrete differentiation techniques.
*/
void apply_disturbance(ripple_p *curr, R_DISTURBANCE *rd);


/* destroy_disturbance() destroys a disturbance. Take care, as they are
   stored in a linked list. Only this module should use this function.
*/
#define destroy_disturbance free


/* When we draw the level, we need the ripples in the form of a bitmap. There
   are two routines for handling this.

   The first is create_ripple_colours(). This takes a pointer to a
   RIPPLE_COLOURS struct, which must already have memory allocated. It fills
   the table with colours for various different angles.

   c[128][128] is the colour of level liquid, where the normal points
   straight upwards. Thus the normal's x- and y-components are both zero.

   As the liquid surface changes angle, the normal is always calculated so
   that the z-component is constant (this is how it comes out of the basic
   formulae). Then the x- and y-components are used as indices into this
   table.

   create_ripple_colours() must be called once for each liquid colour
   required. An RGB_MAP will speed the process up quite drastically.

   The ?r, ?g and ?b parameters specify the various colours of the liquid.
   Each component is in the range 0 to 255.

   hr, hg and hb represent the highlight. This occurs when the liquid surface
   is at just the right angle, at c[64][64]. It is typically white, but can
   be anything. To avoid a highlight, set this to the same as sr, sg and sb.

   sr, sg and sb represent the colour of the water where it reflects the sky.
   lr, lg and lb represent the colour of the water where it reflects the
   level. Note that there will be a gradient that gradually decreases in
   brightness further away from the highlight; specify a bright colour for
   lr, lg and lb, because it will be darkened.

   To avoid having a separate colour for reflecting the level, set lr, lg and
   lb to the same as sr, sg and sb.

   Note that the reflection is only an illusion; the water gradient will be
   taken into account, but the camera angle will not.
*/

typedef struct RIPPLE_COLOURS {
 char c[256][256];
} RIPPLE_COLOURS;

void create_ripple_colours(RIPPLE_COLOURS *rc,
                           int hr, int hg, int hb,
                           int sr, int sg, int sb,
                           int lr, int lg, int lb);

/* Once a ripple colour mapping table has been set up, it can be used with
   draw_ripples() to draw the ripples on to a bitmap ready for use as a
   texture. Pointers to neighbouring tiles, where such tiles exist, are used
   to smooth the transition between tiles.

   This function can take any bitmap whose size is a power of two at least as
   big as the ripple tile's dimensions (RIPPLE_D), though the bitmap must be
   square (not rectangular). A scale shift parameter should be supplied,
   indicating how many times the ripple tile's dimensions must be doubled to
   get the bitmap's size. For example:

   RIPPLE_D = 16, bmp->w = bmp->h =  16, scale = 0
   RIPPLE_D = 16, bmp->w = bmp->h =  32, scale = 1
   RIPPLE_D = 16, bmp->w = bmp->h =  64, scale = 2
   RIPPLE_D = 16, bmp->w = bmp->h = 128, scale = 3

   If you already know the scale parameter, you can calculate the dimensions
   of the required bitmap as (RIPPLE_D << scale).

   In addition to the main ripple tile, you must pass the ripple tiles of the
   eight neighbouring squares, or null if absent. Note that this routine will
   only make diagonal links if at least one of the sides next to that corner
   is present. For example, if rip_l is null and rip_t is null, rip_tl will
   not be used. You do not have to worry about this when calling the
   function.
*/

void draw_ripples(RIPPLE_COLOURS *rc, BITMAP *bmp, int scale,
                  RIPPLE_INFO *ripple_info,
                  RIPPLES *ripples,
                  RIPPLES *rip_l,
                  RIPPLES *rip_r,
                  RIPPLES *rip_t,
                  RIPPLES *rip_b,
                  RIPPLES *rip_tl,
                  RIPPLES *rip_tr,
                  RIPPLES *rip_bl,
                  RIPPLES *rip_br);

#endif /* INCLUDED_RIPPLES_H */
