/* XBubble - sprite.c Copyright (C) 2002 Ivan Djelic 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include "utils.h" #include "rectangle.h" #include "frame.h" #include "sprite.h" extern Display *display; extern Window root; enum SpriteState { SPIRIT_NEW, SPIRIT_UNCHANGED, SPIRIT_CHANGED, SPIRIT_NEEDS_REPAINT, SPIRIT_FINISHED }; struct _Sprite { int layer; int frame; int clock; int x; int y; int cycle; Set overlap; Set animation; XRectangle r; RectangleList redraw_boxes; enum SpriteState state; }; struct _SpritePool { int nb_layers; Set pool; Set *layer; GC redraw_gc; GC normal_gc; }; /*********************** Sprite routines *********************************/ Sprite new_sprite( int layer, int overlap_max, Set a, int frame, int cycle ) { Sprite s = ( Sprite ) xmalloc( sizeof( struct _Sprite )); s->layer = layer; s->animation = a; s->frame = frame; s->cycle = cycle; s->cycle = 1; s->clock = 0; s->r.x = -1; s->r.y = -1; s->x = -1; s->y = -1; s->overlap = set_new(overlap_max); s->redraw_boxes = new_rectangle_list(overlap_max); s->state = SPIRIT_UNCHANGED; return s; } void delete_sprite(Sprite s) { set_free(s->overlap); delete_rectangle_list(s->redraw_boxes); free(s); } void set_sprite_position( Sprite s, int x, int y ) { if (( x != s->x )||( y != s->y )) { s->x = x; s->y = y; if ( s->state == SPIRIT_UNCHANGED ) s->state = SPIRIT_CHANGED; } } void set_sprite_animation( Sprite s, Set a, int frame, int cycle ) { s->animation = a; s->frame = frame; s->clock = 0; s->cycle = cycle; s->cycle = 1; if ( s->state == SPIRIT_UNCHANGED ) s->state = SPIRIT_CHANGED; } Set get_sprite_animation( Sprite s ) { return s->animation; } void set_sprite_layer( Sprite s, int layer ) { if ( layer != s->layer ) { s->layer = layer; if ( s->state == SPIRIT_UNCHANGED ) s->state = SPIRIT_CHANGED; } } void set_sprite_frame( Sprite s, int frame ) { Frame old_frame; Frame new_frame; int frame_max = s->animation->size; if ( frame >= frame_max) frame = ( s->cycle )? frame % frame_max : frame_max-1; if ( s->frame != frame ) { old_frame = s->animation->element[s->frame]; new_frame = s->animation->element[frame]; if (( old_frame->pixmap != new_frame->pixmap )&&( s->state == SPIRIT_UNCHANGED )) s->state = SPIRIT_CHANGED; s->frame = frame; } } void increase_sprite_frame(Sprite s, int increase ) { set_sprite_frame( s, s->frame + increase ); } void increase_sprite_clock(Sprite s, int dt ) { Frame frame = s->animation->element[s->frame]; s->clock += dt; while ( s->clock >= frame->delay ) { s->clock -= frame->delay; set_sprite_frame( s, s->frame + 1 ); frame = s->animation->element[s->frame]; } } /************************ SpritePool routines ******************************/ SpritePool new_sprite_pool( int nb_layers, int nb_sprites_max ) { int i; XGCValues gcv; unsigned long gcm; SpritePool sp = (SpritePool) xmalloc( sizeof( struct _SpritePool )); sp->layer = (Set*) xmalloc( sizeof( Set ) * nb_layers ); sp->pool = set_new( nb_sprites_max ); for ( i = 0; i < nb_layers; i++ ) sp->layer[i] = set_new( nb_sprites_max ); sp->nb_layers = nb_layers; gcm = GCFunction | GCGraphicsExposures; gcv.graphics_exposures = False; gcv.function = GXcopy; sp->normal_gc = XCreateGC( display, root, gcm, &gcv); sp->redraw_gc = XCreateGC( display, root, gcm, &gcv); XSetClipRectangles( display, sp->redraw_gc, 0, 0, NULL, 0, Unsorted ); return sp; } void delete_sprite_pool( SpritePool sp ) { int i; XFreeGC( display, sp->normal_gc); XFreeGC( display, sp->redraw_gc); for ( i = 0; i < sp->nb_layers; i++ ) set_free(sp->layer[i]); set_free(sp->pool); free(sp->layer); free(sp); } void add_sprite_to_pool( SpritePool sp, Sprite s ) { set_add( sp->pool, s); s->state = SPIRIT_NEW; } void remove_sprite_from_pool( SpritePool sp, Sprite s ) { if ( s->state == SPIRIT_NEW ) set_remove( sp->pool, s ); else s->state = SPIRIT_FINISHED; } GC get_sprite_pool_redraw_gc( SpritePool sp ) { return sp->redraw_gc; } /**********************************************************************/ static void draw_sprite( SpritePool sp, Sprite s, Pixmap dest ) { Frame frame = (Frame) s->animation->element[s->frame]; if ( frame->has_mask ) { XSetClipOrigin( display, frame->gc, s->r.x, s->r.y ); XCopyArea( display, frame->pixmap, dest, frame->gc, 0, 0, s->r.width, s->r.height, s->r.x, s->r.y ); } else XCopyArea( display, frame->pixmap, dest, sp->normal_gc, 0, 0, s->r.width, s->r.height, s->r.x, s->r.y ); } static void redraw_sprite( SpritePool sp, Sprite s, Pixmap dest ) { int i; Rectangle *r; Frame frame; GC redraw_gc = sp->normal_gc; frame = (Frame) s->animation->element[s->frame]; if ( frame->has_mask ) { XSetClipOrigin( display, frame->gc, s->r.x, s->r.y ); redraw_gc = frame->gc; } for ( i = 0; i < s->redraw_boxes->size; i++ ) { r = &s->redraw_boxes->element[i]; XCopyArea( display, frame->pixmap, dest, redraw_gc, r->x1, r->y1, r->x2 - r->x1, r->y2 - r->y1, r->x1 + s->r.x, r->y1 + s->r.y ); } } static void sprite_partial_redraw( Sprite s, XRectangle *r ) { int x1, y1, x2, y2, tmp; XRectangle *q = &( s->r ); /* intersect rectangles */ x1 = ( q->x > r->x )? 0 : r->x - q->x; y1 = ( q->y > r->y )? 0 : r->y - q->y; x2 = ( q->x + q->width < r->x + r->width )? q->width : r->x+r->width-q->x; y2 = ( q->y + q->height< r->y + r->height)? q->height : r->y+r->height-q->y; if ( x1 >= x2 ) { tmp = x2; x2 = x1; x1 = tmp; } if ( y1 >= y2 ) { tmp = y2; y2 = y1; y1 = tmp; } add_disjoint_rectangle_to_list( s->redraw_boxes, x1, y1, x2, y2 ); } void redraw_sprite_pool( SpritePool sp, Pixmap pixmap, Pixmap background ) { int i, j, layer; Sprite s, t; Frame frame; XRectangle r; Region redraw_region = XCreateRegion(); /* empty layers */ for ( layer = 0; layer < sp->nb_layers; layer++ ) set_empty( sp->layer[layer] ); /* pass 1 : redraw background */ for ( i = 0; i < sp->pool->size; i++ ) { s = sp->pool->element[i]; /* check if sprite needs to be erased */ if (( s->state == SPIRIT_CHANGED )||( s->state == SPIRIT_FINISHED )) { XUnionRectWithRegion( &(s->r), redraw_region, redraw_region ); /* remove s from overlap sets that won't be recomputed */ for ( j = 0; j < s->overlap->size; j++ ) { t = s->overlap->element[j]; if (( t->state == SPIRIT_UNCHANGED )||( t->state == SPIRIT_NEEDS_REPAINT )) { t->state = SPIRIT_NEEDS_REPAINT; /* partially redraw overlapping sprite */ sprite_partial_redraw( t, &(s->r) ); set_remove( t->overlap, s); } } if ( s->state == SPIRIT_FINISHED ) { set_remove_at( sp->pool, i); i--; } } /* check if sprite needs full drawing */ if (( s->state == SPIRIT_CHANGED )||( s->state == SPIRIT_NEW )) { /* compute new sprite location */ frame = s->animation->element[s->frame]; s->r.width = frame->width; s->r.height = frame->height; s->r.x = s->x - frame->cx; s->r.y = s->y - frame->cy; s->state = SPIRIT_CHANGED; set_empty( s->overlap ); } } /* clear background */ XSetRegion( display, sp->redraw_gc, redraw_region ); XClipBox( redraw_region, &r ); XCopyArea( display, background, pixmap, sp->redraw_gc, r.x, r.y, r.width, r.height, r.x, r.y ); /* at this point, sprites can be in the following states: SPIRIT_CHANGED -> needs full redraw and overlapping set computation. SPIRIT_UNCHANGED SPIRIT_NEEDS_REPAINT -> needs partial redraw. */ /* pass 2 : (re)compute overlapping sets */ for ( i = 0; i < sp->pool->size; i++ ) { s = sp->pool->element[i]; if ( s->state != SPIRIT_UNCHANGED ) { /* sort sprites into layers */ set_add( sp->layer[s->layer], s ); if ( s->state == SPIRIT_CHANGED ) { for ( j = 0; j < sp->pool->size; j++ ) { t = sp->pool->element[j]; if ((( t->state != SPIRIT_CHANGED )||( j > i ))&& /* sprite overlap ? */ ( t->r.x < s->r.x + s->r.width )&& ( t->r.y < s->r.y + s->r.height )&& ( t->r.x + t->r.width > s->r.x )&& ( t->r.y + t->r.height > s->r.y )) { set_add( t->overlap, s); set_add( s->overlap, t); /* partially redraw overlapping sprites on upper layers */ if (( t->layer > s->layer )&&( t->state != SPIRIT_CHANGED )) { t->state = SPIRIT_NEEDS_REPAINT; sprite_partial_redraw( t, &(s->r) ); } } } } } } /* pass 3 : redraw each layer starting from the bottom */ for ( layer = 0; layer < sp->nb_layers; layer++ ) for ( i = 0; i < sp->layer[layer]->size; i++ ) { s = sp->layer[layer]->element[i]; if ( s->state == SPIRIT_CHANGED ) { draw_sprite( sp, s, pixmap ); XUnionRectWithRegion( &(s->r), redraw_region, redraw_region ); } else redraw_sprite( sp, s, pixmap ); s->state = SPIRIT_UNCHANGED; empty_rectangle_list(s->redraw_boxes); } /* make GC */ XSetRegion( display, sp->redraw_gc, redraw_region ); XDestroyRegion( redraw_region ); } void draw_sprite_pool( SpritePool sp, Pixmap pixmap, Pixmap background, int width, int height ) { int i; Sprite s; /* redraw background */ XCopyArea( display, background, pixmap, sp->normal_gc, 0, 0, width, height, 0, 0 ); /* flag all sprites as new */ for ( i = 0; i < sp->pool->size; i++ ) { s = sp->pool->element[i]; s->state = SPIRIT_NEW; } redraw_sprite_pool( sp, pixmap, background ); }