2 * nazghul - an old-school RPG engine
3 * Copyright (C) 2002, 2003 Gordon McNutt
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the Free
7 * Software Foundation; either version 2 of the License, or (at your option)
10 * This program is distributed in the hope that it will be useful, but WITHOUT
11 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
15 * You should have received a copy of the GNU General Public License along with
16 * this program; if not, write to the Free Foundation, Inc., 59 Temple Place,
17 * Suite 330, Boston, MA 02111-1307 USA
20 * gmcnutt@users.sourceforge.net
34 /* rsurf - wraps a surface with a reference count so sprites can share them
35 * without fear of premature calls to SDL_FreeSurface(). */
37 int ref; /* reference count */
38 SDL_Surface *surf; /* underlying surface */
39 char custom : 1; /* NOT referenced by any struct images */
42 /* sprite - animation sequence with different facings */
44 char *tag; /* Script variable name for the sprite. */
45 int n_frames; /* per sequence */
46 int n_total_frames; /* n_frames x # facings */
47 SDL_Rect *frames; /* all frames (sequences must be in order) */
48 struct rsurf *rsurf; /* source of image */
49 int facing; /* current facing sequence */
50 int facings; /* bitmap of supported facing sequences */
51 int sequence; /* current animation sequence */
52 struct sprite *decor; /* decoration sprites */
53 int w_pix, h_pix; /* frame dimensions (in pixels) */
54 int faded : 1; /* render sprite sem-transparent */
55 int wave : 1; /* vertical roll */
59 int ticks_to_next_animation;
62 static int sprite_zoom_factor = 1;
63 static unsigned int sprite_ticks = 0;
65 static struct rsurf *rsurf_new(SDL_Surface *surf)
69 rsurf = (struct rsurf*)calloc(1, sizeof(*rsurf));
78 static void rsurf_unref(struct rsurf *rsurf)
80 assert(rsurf->ref > 0);
83 if (rsurf->surf && rsurf->custom) {
84 SDL_FreeSurface(rsurf->surf);
91 * For reasons I don't quite understand, doing an RGBA->RGBA blit with
92 * SDL_BlitSurface() seems to result in a totally transparent image. I wrote
93 * this as a debug measure, but since it works, and I don't expect it to run in
94 * a performance-critical part of the code, I'm leaving it in for now.
96 * @param source The surface to blit from.
97 * @param from The area of the source to blit from.
98 * @param dest The surface to blit to.
99 * @param to The area of the destination to blit to.
101 static void sprite_custom_blit(SDL_Surface *source, SDL_Rect *from,
102 SDL_Surface *dest, SDL_Rect *to)
104 Uint8 *dpix, *spix, pix = 0;
105 int dx, dy, di, sx, sy, si, spitch, dpitch, pix_bytes;
108 spix = (Uint8*)(source->pixels);
109 dpix = (Uint8*)(dest->pixels);
111 dpitch = dest->pitch;
112 spitch = source->pitch;
114 pix_bytes = source->format->BytesPerPixel;
115 assert(pix_bytes == 1 || pix_bytes == 2 || pix_bytes == 4);
116 assert(dest->format->BytesPerPixel == pix_bytes);
118 for (dy = 0; dy < from->h; dy++) {
120 for (dx = 0; dx < from->w; dx++) {
122 di = (dy + to->y) * dpitch + (dx + to->x) * pix_bytes;
123 si = (sy + from->y) * spitch + (sx + from->x) * pix_bytes;
126 case 4: pix = *(Uint32*)spix; break;
127 case 2: pix = *(Uint16*)spix; break;
128 case 1: pix = *(Uint8 *)spix; break;
130 /* Extract the alpha component of the source pixel. */
131 in_alpha = ((pix & source->format->Amask)
132 >> source->format->Ashift);
134 /* Skip transparent source pixels, leaving destination
136 if (SDL_ALPHA_TRANSPARENT == in_alpha) {
140 /* Do a direct copy of everything else. Note that this
141 * is only correct if the source alpha is opaque. We
142 * really should blend semi-transparent source
145 case 4: *(Uint32*)dpix = pix; break;
146 case 2: *(Uint16*)dpix = pix; break;
147 case 1: *(Uint8 *)dpix = pix; break;
155 * Replace the sprite's current image surface with a reference-counted copy.
157 * @param sprite The sprite to modify.
158 * @returns 0 on success or -1 on error. An error occurs if the surface cannot
161 static int sprite_clone_and_replace_rsurf(struct sprite *sprite)
163 SDL_Surface *dest = 0;
164 SDL_Surface *source = sprite->rsurf->surf;
168 /* Create a new surface so that the original (which may be shared with
169 * other sprites) is not altered. */
170 dest = SDL_CreateRGBSurface(source->flags,
171 sprite->w_pix * sprite->n_total_frames,
173 source->format->BitsPerPixel,
174 source->format->Rmask,
175 source->format->Gmask,
176 source->format->Bmask,
177 source->format->Amask);
179 perror_sdl("SDL_CreateRGBSurface");
183 /* Copy each frame of the sprite to the new surface. */
186 to.w = sprite->w_pix;
187 to.h = sprite->h_pix;
188 for (i = 0; i < sprite->n_total_frames; i++) {
189 to.x = i * sprite->w_pix;
191 /* Blit the frame. */
192 sprite_custom_blit(sprite->rsurf->surf,
196 /* Fixup the frames as we go. */
197 sprite->frames[i] = to;
200 /* If the original surface was a custom rsurf then unref it. */
201 if (sprite->rsurf->custom) {
202 rsurf_unref(sprite->rsurf);
205 /* Stash the surface in a new refcounted surf wrapper. */
206 sprite->rsurf = rsurf_new(dest);
207 sprite->rsurf->custom = 1;
212 static void sprite_blit_faded(SDL_Surface *source, SDL_Rect *from,
215 int dx, dy, di, sx, sy, si, spitch, dpitch;
216 Uint32 *dpix, *spix, pixel;
218 SDL_Surface *tmp = 0;
220 tmp = SDL_CreateRGBSurface(source->flags,
222 source->format->BitsPerPixel,
223 source->format->Rmask,
224 source->format->Gmask,
225 source->format->Bmask,
226 source->format->Amask);
228 perror_sdl("SDL_CreateRGBSurface");
232 dpix = (Uint32 *) tmp->pixels;
233 spix = (Uint32 *) source->pixels;
235 dpitch = tmp->pitch / tmp->format->BytesPerPixel;
236 spitch = source->pitch / source->format->BytesPerPixel;
238 for (dy = 0; dy < from->h; dy++) {
240 for (dx = 0; dx < from->w; dx++) {
242 di = (dy * dpitch + dx);
243 si = (sy + from->y) * spitch + (sx + from->x);
245 /* Cut alpha component in half. */
247 pix_alpha = ((pixel & source->format->Amask)
248 >> source->format->Ashift);
250 pixel &= ~source->format->Amask;
251 pixel |= (pix_alpha << source->format->Ashift);
258 screenBlit(tmp, NULL, to);
259 SDL_FreeSurface(tmp);
262 static void sprite_paint_wave(struct sprite *sprite, int frame, int x, int y)
268 frame = (frame + sprite_ticks) % sprite->n_frames;
270 /* Offset the index into the current sequence */
271 frame += sprite->sequence * sprite->n_frames;
273 // Subtle: when rendering wave sprites zoomed, we'll get artifacts due
274 // to roundoff errors in integer division. Unless we align the
275 // wavecrest to the zoom factor. So for example, if we zoom at a factor
276 // of two then the wavecrest must be a multiple of 2. Since we only
277 // support a zoom factor of 2 right now, the simplest thing to do is
279 wavecrest = (sprite_ticks * 2) % sprite->h_pix;
280 wavecrest = sprite->h_pix - wavecrest; // make it roll south
282 /* Wave sprites are painted in two blits. The first blit copies
283 * everything below the wavecrest to the top part of the onscreen tile.
284 * The second blit copies everything above the wavecrest to the
285 * bottom part of the onscreen tile. This gives the appearance of a
286 * wave rolling over the tile in a direction opposite the wavefront. */
288 src = sprite->frames[frame];
289 src.y += wavecrest; /* fixme -- only works because source
290 * image has one column of sprites */
295 dest.w = sprite->w_pix;
299 sprite_blit_faded(sprite->rsurf->surf, &sprite->frames[frame],
302 screenBlit(sprite->rsurf->surf, &src, &dest);
305 src = sprite->frames[frame];
309 dest.y = dest.y + (sprite->h_pix - wavecrest) /
311 dest.w = sprite->w_pix;
315 sprite_blit_faded(sprite->rsurf->surf, &sprite->frames[frame],
318 screenBlit(sprite->rsurf->surf, &sprite->frames[frame], &dest);
323 static void sprite_paint_normal(struct sprite *sprite, int frame, int x, int y)
329 dest.w = sprite->w_pix;
330 dest.h = sprite->h_pix;
332 #ifndef TEST_PORTRAITS
333 /* dbg hack -- the following code won't work with portraits as sprites;
334 * need a real fix for oversize sprites... */
335 /* If the sprite is larger than a tile, ASSUME (watch out!) we're
336 * blitting a giant character to the map. In this case the bottom of
337 * the sprite will still line up with the bottom of the tile and it
338 * will be horizontally-centered, making the left, right and top
339 * overlap the neighboring tiles. */
340 if (sprite->w_pix > TILE_W) {
341 dest.x -= (sprite->w_pix - TILE_W) / 2;
342 dest.y -= (sprite->h_pix - TILE_H);
345 frame = (frame + sprite_ticks) % sprite->n_frames;
346 frame += sprite->sequence * sprite->n_frames;
349 sprite_blit_faded(sprite->rsurf->surf, &sprite->frames[frame],
352 screenBlit(sprite->rsurf->surf, &sprite->frames[frame], &dest);
357 static void sprite_paint_preframed(struct sprite *sprite, int frame, int x, int y)
363 dest.w = sprite->w_pix;
364 dest.h = sprite->h_pix;
366 /* If the sprite is larger than a tile, ASSUME (watch out!) we're
367 * blitting a giant character to the map. In this case the bottom of
368 * the sprite will still line up with the bottom of the tile and it
369 * will be horizontally-centered, making the left, right and top
370 * overlap the neighboring tiles. */
371 if (sprite->w_pix > TILE_W) {
372 dest.x -= (sprite->w_pix - TILE_W) / 2;
373 dest.y -= (sprite->h_pix - TILE_H);
376 frame += sprite->sequence * sprite->n_frames;
379 sprite_blit_faded(sprite->rsurf->surf, &sprite->frames[frame],
382 screenBlit(sprite->rsurf->surf, &sprite->frames[frame], &dest);
387 static struct sprite * sprite_new_internal(int frames, int facings)
389 struct sprite *sprite;
391 sprite = (struct sprite*)calloc(1, sizeof(*sprite));
395 sprite->n_frames = frames;
396 sprite->facing = SPRITE_DEF_FACING;
397 sprite->facings = facings;
398 sprite->n_total_frames = (sprite->n_frames
400 NUM_PLANAR_DIRECTIONS : 1));
402 // Allocate and initialize the rect structures which index into the
403 // image. One rect per frame of animation. Note that 'facings' is a
404 // bitmask, not a count. Sprites that don't have different facings
405 // specify 'facings' as zero, so for these assume we'll want one
406 // sequence of frames. Sprites that do support facings will need as
407 // many sequences as there are directions supported by the game.
409 sprite->frames = (SDL_Rect*)calloc(sprite->n_total_frames,
411 if (!sprite->frames) {
422 void sprite_del(struct sprite *sprite)
427 free(sprite->frames);
429 sprite_del(sprite->decor);
430 rsurf_unref(sprite->rsurf);
435 void sprite_paint(struct sprite *sprite, int frame, int x, int y)
440 sprite_paint_wave(sprite, frame, x, y);
442 sprite_paint_normal(sprite, frame, x, y);
445 sprite = sprite->decor;
449 void sprite_paint_frame(struct sprite *sprite, int frame, int x, int y)
454 sprite_paint_wave(sprite, frame, x, y);
456 sprite_paint_preframed(sprite, frame, x, y);
459 sprite = sprite->decor;
463 void sprite_advance_ticks(int ticks)
465 Sprite.ticks_to_next_animation -= ticks;
466 if (Sprite.ticks_to_next_animation <= 0) {
467 sprite_advance_frames();
468 cmdwin_repaint_cursor();
470 Sprite.ticks_to_next_animation += AnimationTicks;
474 int sprite_init(void)
476 Sprite.ticks_to_next_animation = 0;
480 void sprite_advance_frames(void)
483 Session->time_stop_ticks++;
491 int sprite_get_facing(struct sprite *sprite)
493 return sprite->facing;
496 int sprite_set_facing(struct sprite *sprite, int facing)
500 if (facing == SPRITE_DEF_FACING) {
501 sprite->sequence = 0;
505 if ((sprite->facings & (1 << facing)) == 0) {
506 dbg("warn: sprite_set_facing: facing=%d invalid for "\
508 facing, sprite->tag);
512 sprite->facing = facing;
513 sprite->sequence = 0;
516 for (i = 0; i < facing; i++) {
518 if (sprite->facings & bit)
525 int sprite_fade(struct sprite *sprite)
531 void sprite_unfade(struct sprite *sprite)
536 void sprite_zoom_out(int factor)
538 sprite_zoom_factor *= factor;
541 extern void sprite_zoom_in(int factor)
543 sprite_zoom_factor /= factor;
546 struct sprite * sprite_new(const char *tag, int frames, int index, int wave,
547 int facings, struct images *images)
549 struct sprite *sprite;
558 sprite = sprite_new_internal(frames, facings);
562 /* Dupe the tag if applicable. */
564 if (!(sprite->tag = strdup(tag)))
568 /* Create a new refcounted surf. */
569 if (!(sprite->rsurf = rsurf_new(images->images)))
573 /* Fill out the rest of the basic fields. */
574 sprite->wave = !!wave;
575 sprite->w_pix = images->w;
576 sprite->h_pix = images->h;
578 /* Fill out the frames based on the index and image info. */
579 col_width = (images->w + images->offx);
580 row_height = (images->h + images->offy);
581 for (i = 0, frame = index;
582 i < sprite->n_total_frames;
584 col = frame % images->cols;
585 row = frame / images->cols;
586 sprite->frames[i].x = col * col_width + images->offx;
587 sprite->frames[i].y = row * row_height + images->offy;
588 sprite->frames[i].w = images->w;
589 sprite->frames[i].h = images->h;
599 struct sprite *sprite_clone(struct sprite *orig, const char *tag)
604 struct sprite *sprite = sprite_new_internal(orig->n_frames,
610 /* Remember the frames pointer before we wipe it out with the copy. */
611 frames = sprite->frames;
613 /* Copy the sprite info. */
614 memcpy(sprite, orig, sizeof(*orig));
616 /* Copy the frames. */
617 sprite->frames = frames;
618 memcpy(sprite->frames, orig->frames,
619 sprite->n_total_frames * sizeof(sprite->frames[0]));
621 /* Bump the refcount on the surface. */
622 sprite->rsurf->ref++;
624 /* Dupe the tag if applicable. */
626 sprite->tag = strdup(tag);
627 } else if (orig->tag) {
628 sprite->tag = strdup(orig->tag);
634 void sprite_append_decoration(struct sprite *base, struct sprite *decor)
637 while (base->decor) {
640 base->decor = sprite_clone(decor, decor->tag);
643 char *sprite_get_tag(struct sprite *sprite)
648 int sprite_is_faded(struct sprite *sprite)
650 return sprite->faded;
653 int sprite_can_face(struct sprite *sprite, int facing)
655 return (sprite->facings & (1 << facing));
658 /* sprite_save - save to file for reload. */
659 void sprite_save(struct sprite *sprite, struct save *save)
661 /* For simple sprites just save the tag. */
662 if (!sprite->decor) {
664 save->write(save, "%s ; sprite\n", sprite->tag);
668 /* For composite sprites */
669 save->write(save, ("(mk-composite-sprite (list "));
672 save->append(save, "%s ", sprite->tag);
673 sprite = sprite->decor;
675 save->append(save, ")) ; composite sprite\n");
678 static void sprite_apply_matrix_to_image(SDL_Surface *source, SDL_Rect *from,
679 SDL_Surface *dest, SDL_Rect *to,
682 Uint8 *dpix, *spix, out_pix, in_pix = 0;
683 int dx, dy, di, sx, sy, si, spitch, dpitch, sbytes, dbytes;
684 Uint8 in_red, in_grn, in_blu, in_alpha, out_red, out_grn, out_blu,
686 int ired, igrn, iblu;
689 spix = (Uint8*)(source->pixels);
690 dpix = (Uint8*)(dest->pixels);
692 dpitch = dest->pitch;
693 spitch = source->pitch;
695 sbytes = source->format->BytesPerPixel;
696 assert(sbytes == 1 || sbytes == 2 || sbytes == 4);
697 dbytes = dest->format->BytesPerPixel;
698 assert(dbytes == 1 || dbytes == 2 || dbytes == 4);
700 /* Make a transparent pixel. If SDL_ALPHA_TRANSPARENT is non-zero that
701 * means transparency is high, so use the mask value. Otherwise zero
702 * means transparent. */
703 if (SDL_ALPHA_TRANSPARENT) {
704 transparent = dest->format->Amask;
709 for (dy = 0; dy < from->h; dy++) {
711 for (dx = 0; dx < from->w; dx++) {
713 di = (dy + to->y) * dpitch + (dx + to->x) * dbytes;
714 si = (sy + from->y) * spitch + (sx + from->x) * sbytes;
717 case 4: in_pix = *(Uint32*)spix; break;
718 case 2: in_pix = *(Uint16*)spix; break;
719 case 1: in_pix = *(Uint8 *)spix; break;
721 /* Extract the alpha component of the source pixel. */
722 in_alpha = ((in_pix & source->format->Amask)
723 >> source->format->Ashift);
725 /* For speed, skip transparent pixels. */
726 if (SDL_ALPHA_TRANSPARENT == in_alpha) {
728 case 4: *(Uint32*)dpix = transparent; break;
729 case 2: *(Uint16*)dpix = transparent; break;
730 case 1: *(Uint8 *)dpix = transparent; break;
735 /* Extract the color components of the source pixel. */
736 in_red = ((in_pix & source->format->Rmask)
737 >> source->format->Rshift);
738 in_grn = ((in_pix & source->format->Gmask)
739 >> source->format->Gshift);
740 in_blu = ((in_pix & source->format->Bmask)
741 >> source->format->Bshift);
743 /* Run the matrix conversion. */
744 ired = (int)((in_red * matrix[0][0])
745 + (in_grn * matrix[0][1])
746 + (in_blu * matrix[0][2])
749 igrn = (int)((in_red * matrix[1][0])
750 + (in_grn * matrix[1][1])
751 + (in_blu * matrix[1][2])
754 iblu = (int)((in_red * matrix[2][0])
755 + (in_grn * matrix[2][1])
756 + (in_blu * matrix[2][2])
759 out_alpha = in_alpha;
761 /* Clamp the result to the allowed range. */
762 out_red = clamp(ired, 0,
763 (int)(dest->format->Rmask
764 >> dest->format->Rshift));
765 out_grn = clamp(igrn, 0,
766 (int)(dest->format->Gmask
767 >> dest->format->Gshift));
768 out_blu = clamp(iblu, 0,
769 (int)(dest->format->Bmask
770 >> dest->format->Bshift));
772 /* Recombine them, along with the original alpha
773 * component, into the destination pixel. */
774 out_pix = (out_red << dest->format->Rshift
775 | out_grn << dest->format->Gshift
776 | out_blu << dest->format->Bshift
777 | out_alpha << dest->format->Ashift);
779 case 4: *(Uint32*)dpix = out_pix; break;
780 case 2: *(Uint16*)dpix = out_pix; break;
781 case 1: *(Uint8 *)dpix = out_pix; break;
787 /* sprite_apply_matrix - applies a color conversion matrix. This is good for
788 * converting monotone or grayscale images into other tones. The matrix is
789 * applied as follows:
791 * r = R*m[0][0] + G*m[0][1] + B*m[0][2] + m[3][0]
792 * g = R*m[1][0] + G*m[1][1] + B*m[1][2] + m[3][1]
793 * b = R*m[2][0] + G*m[2][1] + B*m[2][2] + m[3][2]
796 void sprite_apply_matrix(struct sprite *sprite, float matrix[4][3])
798 SDL_Surface *dest = 0;
799 SDL_Surface *source = sprite->rsurf->surf;
803 /* Create a new surface so that the original (which may be shared with
804 * other sprites) is not altered. */
805 dest = SDL_CreateRGBSurface(source->flags,
806 sprite->w_pix * sprite->n_total_frames,
808 source->format->BitsPerPixel,
809 source->format->Rmask,
810 source->format->Gmask,
811 source->format->Bmask,
812 source->format->Amask);
814 perror_sdl("SDL_CreateRGBSurface");
818 /* Apply the correction to each frame of the sprite. This could be
819 * optimized a bit by applying the matrix in one pass to the entire set
820 * of frames, since they are always contiguous in the image. */
823 to.w = sprite->w_pix;
824 to.h = sprite->h_pix;
825 for (i = 0; i < sprite->n_total_frames; i++) {
826 to.x = i * sprite->w_pix;
828 /* Apply to the image. */
829 sprite_apply_matrix_to_image(sprite->rsurf->surf,
833 /* Fixup the frames as we go. */
834 sprite->frames[i] = to;
837 /* Stash the surface in a new refcounted surf wrapper. */
838 sprite->rsurf = rsurf_new(dest);
839 sprite->rsurf->custom = 1;
842 void sprite_strip_decorations(struct sprite *sprite)
845 /* Decoration sprites are always single-referenced clones, so
846 * blow them away when they're stripped. This will recursively
847 * delete all the trailing decor sprites. */
848 sprite_del(sprite->decor);
853 void sprite_blit_over(struct sprite *dest, struct sprite *src)
857 /* Check preconditions. */
858 assert(dest->w_pix == src->w_pix);
859 assert(dest->h_pix == src->h_pix);
860 assert(dest->n_total_frames == src->n_total_frames);
862 /* Clone the destination sprite's surface before changing it. */
863 if (sprite_clone_and_replace_rsurf(dest))
866 /* For each frame... */
867 for (i = 0; i < dest->n_total_frames; i++) {
869 /* Blit the source over the destination. */
870 sprite_custom_blit(src->rsurf->surf, &src->frames[i],
871 dest->rsurf->surf, &dest->frames[i]);
876 int sprite_num_frames(struct sprite *sprite)
878 return sprite->n_frames;
881 int sprite_facings_list(struct sprite *sprite)
883 return sprite->facings;
886 void sprite_paint_direct(struct sprite *sprite, int frame, SDL_Rect *dest)
888 screenBlit(sprite->rsurf->surf, &sprite->frames[frame], dest);