Subversion Repositories Kolibri OS

Rev

Blame | Last modification | View Log | Download | RSS feed

  1. /*
  2. ------------------------------------------------------------
  3.         Fixed Rate Pig - a fixed logic frame rate demo
  4. ------------------------------------------------------------
  5.  * Copyright (C) 2004 David Olofson <david@olofson.net>
  6.  *
  7.  * This software is released under the terms of the GPL.
  8.  *
  9.  * Contact author for permission if you want to use this
  10.  * software, or work derived from it, under other terms.
  11.  */
  12.  
  13. #include <stdlib.h>
  14. #include <string.h>
  15. #include "engine.h"
  16. #include "SDL_image.h"
  17.  
  18. /* Size of sprite frame table */
  19. #define PIG_MAX_SPRITES         1024
  20.  
  21.  
  22. /*
  23.  * Actually remove an objects. Used internally,
  24.  * to remove objects that have been marked for
  25.  * destruction.
  26.  */
  27. static void close_object(PIG_object *po);
  28.  
  29.  
  30. /*----------------------------------------------------------
  31.         Engine
  32. ----------------------------------------------------------*/
  33. PIG_engine *pig_open(SDL_Surface *screen)
  34. {
  35.         PIG_engine *pe = (PIG_engine *)calloc(1, sizeof(PIG_engine));
  36.         if(!pe)
  37.                 return NULL;
  38.  
  39.         pe->screen = screen;
  40.         if(!pe->screen)
  41.         {
  42.                 pig_close(pe);
  43.                 return NULL;
  44.         }
  45.         if((pe->screen->flags & SDL_HWSURFACE) == SDL_HWSURFACE)
  46.         {
  47.                 pe->buffer = SDL_CreateRGBSurface(SDL_SWSURFACE,
  48.                                 screen->w, screen->h,
  49.                                 screen->format->BitsPerPixel,
  50.                                 screen->format->Rmask,
  51.                                 screen->format->Gmask,
  52.                                 screen->format->Bmask,
  53.                                 screen->format->Amask);
  54.                 if(!pe->buffer)
  55.                 {
  56.                         pig_close(pe);
  57.                         return NULL;
  58.                 }
  59.                 pe->surface = pe->buffer;
  60.         }
  61.         else
  62.                 pe->surface = screen;
  63.  
  64.         pe->pages = 1 + ((screen->flags & SDL_DOUBLEBUF) == SDL_DOUBLEBUF);
  65.  
  66.         pe->interpolation = 1;
  67.         pe->time = 0.0;
  68.         pe->view.w = pe->surface->w;
  69.         pe->view.h = pe->surface->h;
  70.  
  71.         pe->sprites = (PIG_sprite **)calloc(PIG_MAX_SPRITES,
  72.                         sizeof(PIG_sprite *));
  73.         if(!pe->sprites)
  74.         {
  75.                 pig_close(pe);
  76.                 return NULL;
  77.         }
  78.  
  79.         pe->pagedirty[0] = pig_dirty_open(128);
  80.         pe->workdirty = pig_dirty_open(256);
  81.         if(!pe->pagedirty[0] || !pe->workdirty)
  82.         {
  83.                 pig_close(pe);
  84.                 return NULL;
  85.         }
  86.         if(pe->pages > 1)
  87.         {
  88.                 pe->pagedirty[1] = pig_dirty_open(128);
  89.                 if(!pe->pagedirty[1])
  90.                 {
  91.                         pig_close(pe);
  92.                         return NULL;
  93.                 }
  94.         }
  95.  
  96.         return pe;
  97. }
  98.  
  99.  
  100. void pig_close(PIG_engine *pe)
  101. {
  102.         if(pe->sprites)
  103.         {
  104.                 int i;
  105.                 for(i = 0; i < pe->nsprites; ++i)
  106.                         if(pe->sprites[i])
  107.                         {
  108.                                 if(pe->sprites[i]->surface)
  109.                                         SDL_FreeSurface(pe->sprites[i]->surface);
  110.                                 free(pe->sprites[i]);
  111.                         }
  112.                 free(pe->sprites);
  113.         }
  114.         while(pe->objects)
  115.                 close_object(pe->objects);
  116.         if(pe->map)
  117.                 pig_map_close(pe->map);
  118.         if(pe->buffer)
  119.                 SDL_FreeSurface(pe->buffer);
  120.         if(pe->pagedirty[0])
  121.                 pig_dirty_close(pe->pagedirty[0]);
  122.         if(pe->pagedirty[1])
  123.                 pig_dirty_close(pe->pagedirty[1]);
  124.         if(pe->workdirty)
  125.                 pig_dirty_close(pe->workdirty);
  126.         free(pe);
  127. }
  128.  
  129.  
  130. void pig_viewport(PIG_engine *pe, int x, int y, int w, int h)
  131. {
  132.         pe->view.x = x;
  133.         pe->view.y = y;
  134.         pe->view.w = w;
  135.         pe->view.h = h;
  136. }
  137.  
  138.  
  139. int pig_sprites(PIG_engine *pe, const char *filename, int sw, int sh)
  140. {
  141.         int x, y, count, handle;
  142.         SDL_Surface *tmp = IMG_Load(filename);
  143.         if(!tmp)
  144.         {
  145.                 fprintf(stderr, "Could not load '%s'!\n", filename);
  146.                 return -1;
  147.         }
  148.  
  149.         handle = pe->nsprites;
  150.  
  151.         if(!sw)
  152.                 sw = tmp->w;
  153.         if(!sh)
  154.                 sh = tmp->h;
  155.  
  156.         /* Disable blending, so we get the alpha channel COPIED! */
  157.         SDL_SetAlpha(tmp, 0, 0);
  158.  
  159.         count = 0;
  160.         for(y = 0; y <= tmp->h - sh; y += sh)
  161.                 for(x = 0; x <= tmp->w - sw; x += sw)
  162.                 {
  163.                         SDL_Rect r;
  164.                         SDL_Surface *tmp2;
  165.                         PIG_sprite *s;
  166.                         if(pe->nsprites >= PIG_MAX_SPRITES)
  167.                         {
  168.                                 fprintf(stderr, "Sprite bank full!\n");
  169.                                 return -1;
  170.                         }
  171.                         s = (PIG_sprite *)calloc(1, sizeof(PIG_sprite));
  172.                         if(!s)
  173.                                 return -1;
  174.                         s->w = sw;
  175.                         s->h = sh;
  176.                         s->hotx = sw / 2;
  177.                         s->hoty = sh / 2;
  178.                         s->radius = (sw + sh) / 5;
  179.                         tmp2 = SDL_CreateRGBSurface(SDL_SWSURFACE,
  180.                                         sw, sh, 32,
  181.                                         0xff000000, 0x00ff0000,
  182.                                         0x0000ff00, 0x000000ff);
  183.                         SDL_SetAlpha(tmp2, 0, 0);
  184.                         r.x = x;
  185.                         r.y = y;
  186.                         r.w = sw;
  187.                         r.h = sh;
  188.                         SDL_BlitSurface(tmp, &r, tmp2, NULL);
  189.                         SDL_SetAlpha(tmp2, SDL_SRCALPHA | SDL_RLEACCEL,
  190.                                         SDL_ALPHA_OPAQUE);
  191.                         s->surface = SDL_DisplayFormatAlpha(tmp2);
  192.                         if(!s->surface)
  193.                         {
  194.                                 fprintf(stderr, "Could not convert sprite %d"
  195.                                                 " of '%s'!\n",
  196.                                                 count, filename);
  197.                                 return -1;
  198.                         }
  199.                         SDL_FreeSurface(tmp2);
  200.                         pe->sprites[pe->nsprites] = s;
  201.                         ++pe->nsprites;
  202.                         ++count;
  203.                 }
  204.  
  205.         SDL_FreeSurface(tmp);
  206.         return handle;
  207. }
  208.  
  209.  
  210. int pig_hotspot(PIG_engine *pe, int frame, int hotx, int hoty)
  211. {
  212.         if((frame < 0 ) || (frame >= pe->nsprites))
  213.                 return -1;
  214.  
  215.         switch(hotx)
  216.         {
  217.           case PIG_UNCHANGED:
  218.                 break;
  219.           case PIG_MIN:
  220.                 pe->sprites[frame]->hotx = 0;
  221.                 break;
  222.           case PIG_CENTER:
  223.                 pe->sprites[frame]->hotx = pe->sprites[frame]->w / 2;
  224.                 break;
  225.           case PIG_MAX:
  226.                 pe->sprites[frame]->hotx = pe->sprites[frame]->w;
  227.                 break;
  228.           default:
  229.                 pe->sprites[frame]->hotx = hotx;
  230.                 break;
  231.         }
  232.         switch(hoty)
  233.         {
  234.           case PIG_UNCHANGED:
  235.                 break;
  236.           case PIG_MIN:
  237.                 pe->sprites[frame]->hoty = 0;
  238.                 break;
  239.           case PIG_CENTER:
  240.                 pe->sprites[frame]->hoty = pe->sprites[frame]->h / 2;
  241.                 break;
  242.           case PIG_MAX:
  243.                 pe->sprites[frame]->hoty = pe->sprites[frame]->h;
  244.                 break;
  245.           default:
  246.                 pe->sprites[frame]->hoty = hoty;
  247.                 break;
  248.         }
  249.         return 0;
  250. }
  251.  
  252.  
  253. int pig_radius(PIG_engine *pe, int frame, int radius)
  254. {
  255.         if((frame < 0 ) || (frame >= pe->nsprites))
  256.                 return -1;
  257.  
  258.         pe->sprites[frame]->radius = radius;
  259.         return 0;
  260. }
  261.  
  262.  
  263. void pig_start(PIG_engine *pe, int frame)
  264. {
  265.         PIG_object *po = pe->objects;
  266.         pe->time = (double)frame;
  267.         pe->frame = frame;
  268.         while(po)
  269.         {
  270.                 po->ip.gx = po->ip.ox = po->x;
  271.                 po->ip.gy = po->ip.oy = po->y;
  272.                 po->ip.gimage = po->ibase + po->image;
  273.                 po = po->next;
  274.         }
  275. }
  276.  
  277.  
  278. static void run_timers(PIG_engine *pe, PIG_object *po)
  279. {
  280.         int i;
  281.         for(i = 0; i < PIG_TIMERS; ++i)
  282.                 if(po->timer[i])
  283.                 {
  284.                         --po->timer[i];
  285.                         if(!po->timer[i])
  286.                         {
  287.                                 PIG_event ev;
  288.                                 ev.type = PIG_TIMER0 + i;
  289.                                 po->handler(po, &ev);
  290.                                 if(!po->id)
  291.                                         return;
  292.                         }
  293.                 }
  294. }
  295.  
  296.  
  297. static void test_offscreen(PIG_engine *pe, PIG_object *po, PIG_sprite *s)
  298. {
  299.         PIG_event ev;
  300.         int hx, hy, w, h;
  301.         if(s)
  302.         {
  303.                 hx = s->hotx;
  304.                 hy = s->hoty;
  305.                 w = s->w;
  306.                 h = s->h;
  307.         }
  308.         else
  309.                 hx = hy = w = h = 0;
  310.         ev.cinfo.sides = (po->y - hy < -h) << PIG_TOP_B;
  311.         ev.cinfo.sides |= (po->y - hy >= pe->view.h) << PIG_BOTTOM_B;
  312.         ev.cinfo.sides |= (po->x - hx < -w) << PIG_LEFT_B;
  313.         ev.cinfo.sides |= (po->x - hx >= pe->view.w) << PIG_RIGHT_B;
  314.         if(ev.cinfo.sides)
  315.         {
  316.                 float dx = po->x - po->ip.ox;
  317.                 float dy = po->y - po->ip.oy;
  318.                 if(ev.cinfo.sides & PIG_TOP)
  319.                 {
  320.                         ev.cinfo.y = 0;
  321.                         if(dy)
  322.                                 ev.cinfo.x = po->ip.ox - dx * po->ip.oy / dy;
  323.                 }
  324.                 else if(ev.cinfo.sides & PIG_BOTTOM)
  325.                 {
  326.                         ev.cinfo.y = pe->view.h - 1;
  327.                         if(dy)
  328.                                 ev.cinfo.x = po->ip.ox + dx *
  329.                                                 (ev.cinfo.y - po->ip.oy) / dy;
  330.                 }
  331.                 if(ev.cinfo.sides & PIG_LEFT)
  332.                 {
  333.                         ev.cinfo.x = 0;
  334.                         if(dx)
  335.                                 ev.cinfo.y = po->ip.oy - dy * po->ip.ox / dx;
  336.                 }
  337.                 else if(ev.cinfo.sides & PIG_RIGHT)
  338.                 {
  339.                         ev.cinfo.x = pe->view.w - 1;
  340.                         if(dx)
  341.                                 ev.cinfo.y = po->ip.oy + dy *
  342.                                                 (ev.cinfo.x - po->ip.ox) / dx;
  343.                 }
  344.                 ev.type = PIG_OFFSCREEN;
  345.                 po->handler(po, &ev);
  346.         }
  347. }
  348.  
  349.  
  350. /* Test for stationary sprite/sprite collision */
  351. static void sprite_sprite_one(PIG_object *po, PIG_object *po2, float t, float hitdist)
  352. {
  353.         float dx, dy, dsquare;
  354.         PIG_event ev;
  355.         int sides;
  356.         float ix = po->ip.ox * (1 - t) + po->x * t;
  357.         float iy = po->ip.oy * (1 - t) + po->y * t;
  358.         float ix2 = po2->ip.ox * (1 - t) + po2->x * t;
  359.         float iy2 = po2->ip.oy * (1 - t) + po2->y * t;
  360.         dx = ix - ix2;
  361.         dy = iy - iy2;
  362.         dsquare = dx*dx + dy*dy;
  363.         if(dsquare >= hitdist*hitdist)
  364.                 return;         /* Nothing... --> */
  365.  
  366.         if(fabs(dsquare) < 1)
  367.                 sides = PIG_ALL;
  368.         else
  369.         {
  370.                 float d = sqrt(dsquare);
  371.                 dx /= d;
  372.                 dy /= d;
  373.                 if(dx < -0.707)
  374.                         sides = PIG_LEFT;
  375.                 else if((dx > 0.707))
  376.                         sides = PIG_RIGHT;
  377.                 else
  378.                         sides = 0;
  379.                 if(dy < -0.707)
  380.                         sides |= PIG_TOP;
  381.                 else if((dy > 0.707))
  382.                         sides |= PIG_BOTTOM;
  383.         }
  384.         ev.type = PIG_HIT_OBJECT;
  385.         ev.cinfo.ff = 0.0;
  386.  
  387.         ev.cinfo.x = ix;
  388.         ev.cinfo.y = iy;
  389.         ev.cinfo.sides = sides;
  390.         if(po->hitmask & po2->hitgroup)
  391.         {
  392.                 ev.obj = po2;
  393.                 po->handler(po, &ev);
  394.         }
  395.  
  396.         if(po2->id && (po2->hitmask & po->hitgroup))
  397.         {
  398.                 int s;
  399.                 ev.cinfo.x = ix2;
  400.                 ev.cinfo.y = iy2;
  401.                 s = ((sides >> PIG_LEFT_B) & 1) << PIG_RIGHT_B;
  402.                 s |= ((sides >> PIG_RIGHT_B) & 1) << PIG_LEFT_B;
  403.                 s |= ((sides >> PIG_TOP_B) & 1) << PIG_BOTTOM_B;
  404.                 s |= ((sides >> PIG_BOTTOM_B) & 1) << PIG_TOP_B;
  405.                 ev.cinfo.sides = s;
  406.                 ev.obj = po;
  407.                 po2->handler(po2, &ev);
  408.         }
  409. }
  410.  
  411.  
  412. /*
  413.  * Check 'po' against all subsequent objects in the list.
  414.  * The testing is step size limited so that neither object
  415.  * moves more than 25% of the collision distance between tests.
  416.  * (25% should be sufficient for correct direction flags.)
  417.  */
  418. static void test_sprite_sprite(PIG_engine *pe, PIG_object *po, PIG_sprite *s)
  419. {
  420.         int image;
  421.         PIG_object *po2, *next2;
  422.         for(po2 = po->next; po2; po2 = next2)
  423.         {
  424.                 float hitdist, d, dmax, t, dt;
  425.                 next2 = po2->next;
  426.                 if(!po->id || !po2->id)
  427.                         break;
  428.  
  429.                 /* Check collision groups and masks */
  430.                 if(!(po->hitmask & po2->hitgroup) &&
  431.                                 !(po2->hitmask & po->hitgroup))
  432.                         continue;
  433.  
  434.                 /* Calculate minimum distance */
  435.                 hitdist = s ? s->radius : 0;
  436.                 image = po2->ibase + po2->image;
  437.                 if((image >= 0) && (image < pe->nsprites))
  438.                         hitdist += pe->sprites[image]->radius;
  439.                 if(hitdist < 1)
  440.                         hitdist = 1;
  441.  
  442.                 /* Calculate number of testing steps */
  443.                 dmax = fabs(po->ip.ox - po->x);
  444.                 d = fabs(po->ip.oy - po->y);
  445.                 dmax = d > dmax ? d : dmax;
  446.                 d = fabs(po2->ip.ox - po2->x);
  447.                 dmax = d > dmax ? d : dmax;
  448.                 d = fabs(po2->ip.oy - po2->y);
  449.                 dmax = d > dmax ? d : dmax;
  450.                 if(dmax > 1)
  451.                         dt = hitdist / (dmax * 4);
  452.                 else
  453.                         dt = 1;
  454.  
  455.                 /* Sweep test! */
  456.                 for(t = 0; t < 1; t += dt)
  457.                         sprite_sprite_one(po, po2, t, hitdist);
  458.         }
  459. }
  460.  
  461.  
  462. /*
  463.  * Returns a non-zero value if the tile at (x, y) is marked for
  464.  * collisions on the side indicated by 'mask'.
  465.  */
  466. static __inline__ int check_tile(PIG_map *m, int x, int y, int mask)
  467. {
  468.         int mx, my;
  469.         /*
  470.          * Must check < 0 first! (Division rounds
  471.          * towards zero - not downwards.)
  472.          */
  473.         if(x < 0 || y < 0)
  474.                 return PIG_NONE;
  475.  
  476.         mx = x / m->tw;
  477.         my = y / m->th;
  478.         if(mx >= m->w || my >= m->h)
  479.                 return PIG_NONE;
  480.  
  481.         return m->hit[my * m->w + mx] & mask;
  482. }
  483.  
  484.  
  485. int pig_test_map(PIG_engine *pe, int x, int y)
  486. {
  487.         int mx, my;
  488.         if(x < 0 || y < 0)
  489.                 return PIG_NONE;
  490.  
  491.         mx = x / pe->map->tw;
  492.         my = y / pe->map->th;
  493.         if(mx >= pe->map->w || my >= pe->map->h)
  494.                 return PIG_NONE;
  495.  
  496.         return pe->map->hit[my * pe->map->w + mx];
  497. }
  498.  
  499.  
  500. /*
  501.  * Simple implementation that checks only for top edge collisions.
  502.  * (Full top/bottom/left/right checks with proper handling of
  503.  * corners and rows of tiles is a lot more complicated, so I'll
  504.  * leave that out for now, rather than hacking something simple
  505.  * but incorrect.)
  506.  */
  507. int pig_test_map_vector(PIG_engine *pe, int x1, int y1, int x2, int y2,
  508.                 int mask, PIG_cinfo *ci)
  509. {
  510.         PIG_cinfo lci;
  511.         PIG_map *m = pe->map;
  512.         int x, y;
  513.         int dist = 2000000000L;
  514.         if(!ci)
  515.                 ci = &lci;
  516.         ci->sides = 0;
  517.         if((mask & PIG_TOP) && (y1 < y2))
  518.         {
  519.                 /* Test for tiles that can be hit from the top */
  520.                 for(y = y1 + m->th - y1 % m->th; y <= y2; y += m->th)
  521.                 {
  522.                         x = x1 + (x2 - x1) * (y - y1) / (y2 - y1);
  523.                         if(check_tile(m, x, y + 1, PIG_TOP))
  524.                         {
  525.                                 dist = (x-x1) * (x-x1) + (y-y1) * (y-y1);
  526.                                 ci->x = x;
  527.                                 ci->y = y - 1;
  528.                                 ci->sides |= PIG_TOP;
  529.                                 break;
  530.                         }
  531.                 }
  532.         }
  533.         if(ci->sides)
  534.                 ci->ff = sqrt((x2 - x1) * (x2 - x1) +
  535.                                 (y2 - y1) * (y2 - y1) / dist);
  536.         return ci->sides;
  537. }
  538.  
  539.  
  540. static void test_sprite_map(PIG_engine *pe, PIG_object *po, PIG_sprite *s)
  541. {
  542.         PIG_event ev;
  543.         if(pig_test_map_vector(pe, po->ip.ox, po->ip.oy, po->x, po->y,
  544.                         po->tilemask, &ev.cinfo))
  545.         {
  546.                 ev.type = PIG_HIT_TILE;
  547.                 po->handler(po, &ev);
  548.         }
  549. }
  550.  
  551.  
  552. static void run_logic(PIG_engine *pe)
  553. {
  554.         PIG_object *po, *next;
  555.         int image;
  556.  
  557.         /* Shift logic coordinates */
  558.         for(po = pe->objects; po; po = po->next)
  559.         {
  560.                 po->ip.ox = po->x;
  561.                 po->ip.oy = po->y;
  562.         }
  563.  
  564.         if(pe->before_objects)
  565.                 pe->before_objects(pe);
  566.  
  567.         for(po = pe->objects; po; po = next)
  568.         {
  569.                 PIG_event ev;
  570.                 /*
  571.                  * We must grab the next pointer before
  572.                  * we call any event handlers, as they
  573.                  * may cause objects to remove themselves!
  574.                  */
  575.                 next = po->next;
  576.                 ev.type = PIG_PREFRAME;
  577.                 po->handler(po, &ev);
  578.         }
  579.  
  580.         for(po = pe->objects; po; po = next)
  581.         {
  582.                 PIG_sprite *s;
  583.                 next = po->next;
  584.                 image = po->ibase + po->image;
  585.                 if((image >= 0) && (image < pe->nsprites))
  586.                         s = pe->sprites[image];
  587.                 else
  588.                         s = NULL;
  589.  
  590.                 /* Move! */
  591.                 po->vx += po->ax;
  592.                 po->vy += po->ay;
  593.                 po->x += po->vx;
  594.                 po->y += po->vy;
  595.  
  596.                 /* Check and handle events */
  597.                 if(po->handler)
  598.                 {
  599.                         run_timers(pe, po);
  600.                         if(po->id)
  601.                                 test_offscreen(pe, po, s);
  602.                         if(po->id && (po->hitmask || po->hitgroup))
  603.                                 test_sprite_sprite(pe, po, s);
  604.                         if(po->id && po->tilemask)
  605.                                 test_sprite_map(pe, po, s);
  606.  
  607.                 }
  608.         }
  609.  
  610.         for(po = pe->objects; po; po = next)
  611.         {
  612.                 next = po->next;
  613.                 if(po->id)
  614.                 {
  615.                         PIG_event ev;
  616.                         ev.type = PIG_POSTFRAME;
  617.                         po->handler(po, &ev);
  618.                         ++po->age;
  619.                 }
  620.         }
  621.  
  622.         if(pe->after_objects)
  623.                 pe->after_objects(pe);
  624. }
  625.  
  626.  
  627. void pig_animate(PIG_engine *pe, float frames)
  628. {
  629.         /* Advance logic time */
  630.         int i = floor(pe->time + frames) - floor(pe->time);
  631.         while(i--)
  632.         {
  633.                 run_logic(pe);
  634.                 ++pe->frame;
  635.         }
  636.         pe->time += frames;
  637. }
  638.  
  639.  
  640. void pig_dirty(PIG_engine *pe, SDL_Rect *dr)
  641. {
  642.         SDL_Rect r;
  643.         r.x = 0;
  644.         r.y = 0;
  645.         r.w = pe->surface->w;
  646.         r.h = pe->surface->h;
  647.         if(dr)
  648.                 pig_intersectrect(dr, &r);
  649.         if(r.w && r.h)
  650.                 pig_dirty_add(pe->pagedirty[pe->page], &r);
  651. }
  652.  
  653.  
  654. static void tile_area(PIG_engine *pe, SDL_Rect *r)
  655. {
  656.         SDL_Rect cr;
  657.         int x, y, startx, starty, maxx, maxy, tilesperrow;
  658.         cr = *r;
  659.         cr.x += pe->view.x;
  660.         cr.y += pe->view.y;
  661.         SDL_SetClipRect(pe->surface, &cr);
  662.  
  663.         startx = r->x / pe->map->tw;
  664.         starty = r->y / pe->map->th;
  665.         maxx = (r->x + r->w + pe->map->tw - 1) / pe->map->tw;
  666.         maxy = (r->y + r->h + pe->map->th - 1) / pe->map->th;
  667.         if(maxx > pe->map->w - 1)
  668.                 maxx = pe->map->w - 1;
  669.         if(maxy > pe->map->h - 1)
  670.                 maxy = pe->map->h - 1;
  671.         tilesperrow = pe->map->tiles->w / pe->map->tw;
  672.  
  673.         for(y = starty; y <= maxy; ++y)
  674.                 for(x = startx; x <= maxx; ++x)
  675.                 {
  676.                         SDL_Rect from, to;
  677.                         int c = pe->map->map[y * pe->map->w + x];
  678.                         from.x = c % tilesperrow * pe->map->tw;
  679.                         from.y = c / tilesperrow * pe->map->th;
  680.                         from.w = pe->map->tw;
  681.                         from.h = pe->map->th;
  682.                         to.x = pe->view.x + x * pe->map->tw;
  683.                         to.y = pe->view.y + y * pe->map->th;
  684.                         SDL_BlitSurface(pe->map->tiles, &from,
  685.                                         pe->surface, &to);
  686.                 }
  687. }
  688.  
  689.  
  690. void remove_sprites(PIG_engine *pe)
  691. {
  692.         SDL_Rect r;
  693.         PIG_sprite *s;
  694.         PIG_object *po, *next;
  695.  
  696.         /*
  697.          * Remove all objects, using the information that
  698.          * remains from the last frame. The actual removal
  699.          * is done by drawing over the sprites with tiles
  700.          * from the map.
  701.          *
  702.          * We assume that most objects don't overlap. If
  703.          * they do that a lot, we could use a "dirty map"
  704.          * to avoid rendering the same tiles multiple times
  705.          * in the overlapping areas.
  706.          */
  707.         for(po = pe->objects; po; po = next)
  708.         {
  709.                 next = po->next;
  710.                 if((po->ip.gimage < 0) || (po->ip.gimage >= pe->nsprites))
  711.                         continue;
  712.                 s = pe->sprites[po->ip.gimage];
  713.                 r.x = po->ip.gx - s->hotx;
  714.                 r.y = po->ip.gy - s->hoty;
  715.                 r.w = s->w;
  716.                 r.h = s->h;
  717.                 pig_intersectrect(&pe->view, &r);
  718.                 if(r.w && r.h)
  719.                         tile_area(pe, &r);
  720.  
  721.                 /*
  722.                  * Delete dead objects *after* they've
  723.                  * been removed from the rendering buffer!
  724.                  */
  725.                 if(!po->id)
  726.                         close_object(po);
  727.         }
  728. }
  729.  
  730.  
  731. void draw_sprites(PIG_engine *pe)
  732. {
  733.         PIG_dirtytable *pdt;
  734.         PIG_sprite *s;
  735.         PIG_object *po;
  736.         float fframe = pe->time - floor(pe->time);
  737.         SDL_SetClipRect(pe->surface, &pe->view);
  738.  
  739.         /* Swap the work and display/back page dirtytables */
  740.         pdt = pe->workdirty;
  741.         pe->workdirty = pe->pagedirty[pe->page];
  742.         pe->pagedirty[pe->page] = pdt;
  743.  
  744.         /* Clear the display/back page dirtytable */
  745.         pdt->count = 0;
  746.  
  747.         /* Update positions and render all objects */
  748.         po = pe->objects;
  749.         while(po)
  750.         {
  751.                 /* Calculate graphic coordinates */
  752.                 if(pe->interpolation)
  753.                 {
  754.                         po->ip.gx = po->ip.ox * (1 - fframe) + po->x * fframe;
  755.                         po->ip.gy = po->ip.oy * (1 - fframe) + po->y * fframe;
  756.                 }
  757.                 else
  758.                 {
  759.                         po->ip.gx = po->x;
  760.                         po->ip.gy = po->y;
  761.                 }
  762.                 po->ip.gimage = po->ibase + po->image;
  763.  
  764.                 /* Render the sprite! */
  765.                 if((po->ip.gimage >= 0) && (po->ip.gimage < pe->nsprites))
  766.                 {
  767.                         SDL_Rect dr;
  768.                         s = pe->sprites[po->ip.gimage];
  769.                         dr.x = po->ip.gx - s->hotx + pe->view.x;
  770.                         dr.y = po->ip.gy - s->hoty + pe->view.y;
  771.                         SDL_BlitSurface(pe->sprites[po->ip.gimage]->surface,
  772.                                 NULL, pe->surface, &dr);
  773.                         /*
  774.                          * We use the clipped rect for the dirtyrect!
  775.                          */
  776.                         if(dr.w && dr.h)
  777.                                 pig_dirty_add(pdt, &dr);
  778.                 }
  779.                 po = po->next;
  780.         }
  781.  
  782.         /* Merge the display/back page table into the work table */
  783.         pig_dirty_merge(pe->workdirty, pdt);
  784. }
  785.  
  786.  
  787. void pig_refresh(PIG_engine *pe)
  788. {
  789.         remove_sprites(pe);
  790.         draw_sprites(pe);
  791. }
  792.  
  793.  
  794. void pig_refresh_all(PIG_engine *pe)
  795. {
  796.         tile_area(pe, &pe->view);
  797.         pig_dirty(pe, NULL);
  798.         draw_sprites(pe);
  799. }
  800.  
  801.  
  802. static void show_rects(PIG_engine *pe, PIG_dirtytable *pdt)
  803. {
  804.         int i;
  805.         Uint32 color;
  806.         if(!pe->buffer)
  807.         {
  808.                 pe->buffer = SDL_CreateRGBSurface(SDL_SWSURFACE,
  809.                                 pe->screen->w, pe->screen->h,
  810.                                 pe->screen->format->BitsPerPixel,
  811.                                 pe->screen->format->Rmask,
  812.                                 pe->screen->format->Gmask,
  813.                                 pe->screen->format->Bmask,
  814.                                 pe->screen->format->Amask);
  815.                 if(!pe->buffer)
  816.                         return;
  817.                 pe->surface = pe->buffer;
  818.                 tile_area(pe, &pe->view);
  819.         }
  820.         if(!pe->buffer)
  821.                 return;
  822.  
  823.         pe->direct = 0;
  824.  
  825.         for(i = 0; i < pdt->count; ++i)
  826.         {
  827.                 SDL_Rect r;
  828.                 r = pdt->rects[i];
  829.                 r.x -= 32;
  830.                 r.y -= 32;
  831.                 r.w += 64;
  832.                 r.h += 64;
  833.                 SDL_BlitSurface(pe->buffer, &r, pe->screen, &r);
  834.         }
  835.  
  836.         color = SDL_MapRGB(pe->screen->format, 255, 0, 255);
  837.         for(i = 0; i < pdt->count; ++i)
  838.         {
  839.                 SDL_Rect r;
  840.                 r = pdt->rects[i];
  841.                 r.h = 1;
  842.                 SDL_FillRect(pe->screen, &r, color);
  843.                 r.y += pdt->rects[i].h - 1;
  844.                 SDL_FillRect(pe->screen, &r, color);
  845.                 r = pdt->rects[i];
  846.                 r.w = 1;
  847.                 SDL_FillRect(pe->screen, &r, color);
  848.                 r.x += pdt->rects[i].w - 1;
  849.                 SDL_FillRect(pe->screen, &r, color);
  850.         }
  851. }
  852.  
  853.  
  854. void pig_flip(PIG_engine *pe)
  855. {
  856.         PIG_dirtytable *pdt = pe->workdirty;
  857.         int i;
  858.         SDL_SetClipRect(pe->surface, NULL);
  859.  
  860.         if(pe->show_dirtyrects)
  861.         {
  862.                 show_rects(pe, pdt);
  863.                 for(i = 0; i < pdt->count; ++i)
  864.                 {
  865.                         pdt->rects[i].x -= 32;
  866.                         pdt->rects[i].y -= 32;
  867.                         pdt->rects[i].w += 64;
  868.                         pdt->rects[i].h += 64;
  869.                         pig_intersectrect(&pe->buffer->clip_rect, &pdt->rects[i]);
  870.                 }
  871.         }
  872.         else if(pe->surface == pe->buffer)
  873.                 for(i = 0; i < pdt->count; ++i)
  874.                         SDL_BlitSurface(pe->buffer, pdt->rects + i,
  875.                                         pe->screen, pdt->rects + i);
  876.  
  877.         if((pe->screen->flags & SDL_HWSURFACE) == SDL_HWSURFACE)
  878.         {
  879.                 SDL_Flip(pe->screen);
  880.                 if(pe->pages > 1)
  881.                         pe->page = 1 - pe->page;
  882.         }
  883.         else
  884.                 SDL_UpdateRects(pe->screen, pdt->count, pdt->rects);
  885.  
  886.         if(pe->direct)
  887.                 pe->surface = pe->screen;
  888.         else
  889.                 pe->surface = pe->buffer ? pe->buffer : pe->screen;
  890. }
  891.  
  892.  
  893. void pig_draw_sprite(PIG_engine *pe, int frame, int x, int y)
  894. {
  895.         SDL_Rect dr;
  896.         if(frame >= pe->nsprites)
  897.                 return;
  898.         dr.x = x - pe->sprites[frame]->hotx + pe->view.x;
  899.         dr.y = y - pe->sprites[frame]->hoty + pe->view.y;
  900.         SDL_BlitSurface(pe->sprites[frame]->surface, NULL,
  901.                         pe->surface, &dr);
  902. }
  903.  
  904.  
  905. /*----------------------------------------------------------
  906.         Map
  907. ----------------------------------------------------------*/
  908. PIG_map *pig_map_open(PIG_engine *pe, int w, int h)
  909. {
  910.         if(pe->map)
  911.                 pig_map_close(pe->map);
  912.  
  913.         pe->map = (PIG_map *)calloc(1, sizeof(PIG_map));
  914.         if(!pe->map)
  915.                 return NULL;
  916.  
  917.         pe->map->owner = pe;
  918.         pe->map->w = w;
  919.         pe->map->h = h;
  920.         pe->map->hit = (unsigned char *)calloc(w, h);
  921.         if(!pe->map->hit)
  922.         {
  923.                 pig_map_close(pe->map);
  924.                 return NULL;
  925.         }
  926.         pe->map->map = (unsigned char *)calloc(w, h);
  927.         if(!pe->map->map)
  928.         {
  929.                 pig_map_close(pe->map);
  930.                 return NULL;
  931.         }
  932.         return pe->map;
  933. }
  934.  
  935.  
  936. void pig_map_close(PIG_map *pm)
  937. {
  938.         PIG_engine *pe = pm->owner;
  939.         if(pm->tiles)
  940.                 SDL_FreeSurface(pm->tiles);
  941.         free(pm->hit);
  942.         free(pm->map);
  943.         free(pe->map);
  944.         pe->map = NULL;
  945. }
  946.  
  947.  
  948. int pig_map_tiles(PIG_map *pm, const char *filename, int tw, int th)
  949. {
  950.         SDL_Surface *tmp;
  951.         pm->tw = tw;
  952.         pm->th = th;
  953.         tmp = IMG_Load(filename);
  954.         if(!tmp)
  955.         {
  956.                 fprintf(stderr, "Could not load '%s'!\n", filename);
  957.                 return -1;
  958.         }
  959.         pm->tiles = SDL_DisplayFormat(tmp);
  960.         if(!pm->tiles)
  961.         {
  962.                 fprintf(stderr, "Could not convert '%s'!\n", filename);
  963.                 return -1;
  964.         }
  965.         SDL_FreeSurface(tmp);
  966.         return 0;
  967. }
  968.  
  969.  
  970. void pig_map_collisions(PIG_map *pm, unsigned first, unsigned count, PIG_sides sides)
  971. {
  972.         int i;
  973.         if(first > 255)
  974.                 return;
  975.         if(first + count > 255)
  976.                 count = 255 - first;
  977.         for(i = first; i < first + count; ++i)
  978.                 pm->hitinfo[i] = sides;
  979. }
  980.  
  981.  
  982. /*
  983.  * Load a map from a string (one byte/tile). 'trans'
  984.  * is a string used for translating 'data' into integer
  985.  * tile indices. Each position in 'trans' corresponds
  986.  * to one tile in the tile palette.
  987.  */
  988. int pig_map_from_string(PIG_map *pm, const char *trans, const char *data)
  989. {
  990.         int x, y, z;
  991.  
  992.         /* Load the map */
  993.         z = 0;
  994.         for(y = 0; y < pm->h; ++y)
  995.                 for(x = 0; x < pm->w; ++x)
  996.                 {
  997.                         const char *f;
  998.                         int c = data[z];
  999.                         if(!c)
  1000.                         {
  1001.                                 fprintf(stderr, "Map string too short!\n");
  1002.                                 return -1;
  1003.                         }
  1004.                         f = strchr(trans, c);
  1005.                         if(!f)
  1006.                         {
  1007.                                 fprintf(stderr, "Character '%c' not in"
  1008.                                                 " the translation string!\n",
  1009.                                                 c);
  1010.                                 return -1;
  1011.                         }
  1012.                         pm->map[z] = f - trans;
  1013.                         ++z;
  1014.                 }
  1015.         /* Generate collision map */
  1016.         for(y = 0; y < pm->h; ++y)
  1017.                 for(x = 0; x < pm->w; ++x)
  1018.                         pm->hit[y * pm->w + x] =
  1019.                                         pm->hitinfo[pm->map[y * pm->w + x]];
  1020.         return 0;
  1021. }
  1022.  
  1023.  
  1024. /*----------------------------------------------------------
  1025.         Object
  1026. ----------------------------------------------------------*/
  1027.  
  1028.  
  1029. static PIG_object *get_object(PIG_engine *pe)
  1030. {
  1031.         PIG_object *po;
  1032.         if(pe->object_pool)
  1033.         {
  1034.                 po = pe->object_pool;
  1035.                 pe->object_pool = po->next;
  1036.                 memset(po, 0, sizeof(PIG_object));
  1037.         }
  1038.         else
  1039.         {
  1040.                 po = (PIG_object *)calloc(1, sizeof(PIG_object));
  1041.                 if(!po)
  1042.                         return NULL;
  1043.         }
  1044.         po->id = ++pe->object_id_counter;
  1045.         return po;
  1046. }
  1047.  
  1048.  
  1049. static void free_object(PIG_object *po)
  1050. {
  1051.         po->prev = NULL;
  1052.         po->next = po->owner->object_pool;
  1053.         po->owner->object_pool = po;
  1054.         po->id = 0;
  1055. }
  1056.  
  1057.  
  1058. PIG_object *pig_object_open(PIG_engine *pe, int x, int y, int last)
  1059. {
  1060.         PIG_object *po = get_object(pe);
  1061.         if(!po)
  1062.                 return NULL;
  1063.  
  1064.         po->owner = pe;
  1065.         po->tilemask = PIG_ALL;
  1066.         po->hitmask = 0;
  1067.         po->hitgroup = 0;
  1068.  
  1069.         if(last && pe->objects)
  1070.         {
  1071.                 PIG_object *lo = pe->objects;
  1072.                 while(lo->next)
  1073.                         lo = lo->next;
  1074.                 po->prev = lo;
  1075.                 po->next = NULL;
  1076.                 lo->next = po;
  1077.         }
  1078.         else
  1079.         {
  1080.                 po->prev = NULL;
  1081.                 po->next = pe->objects;
  1082.                 if(po->next)
  1083.                         po->next->prev = po;
  1084.                 pe->objects = po;
  1085.         }
  1086.  
  1087.         po->x = x;
  1088.         po->y = y;
  1089.         po->ip.ox = x;
  1090.         po->ip.oy = y;
  1091.         return po;
  1092. }
  1093.  
  1094.  
  1095. static void close_object(PIG_object *po)
  1096. {
  1097.         if(po == po->owner->objects)
  1098.                 po->owner->objects = po->next;
  1099.         else if(po->prev)
  1100.                 po->prev->next = po->next;
  1101.         if(po->next)
  1102.                 po->next->prev = po->prev;
  1103.         free_object(po);
  1104. }
  1105.  
  1106.  
  1107. void pig_object_close(PIG_object *po)
  1108. {
  1109.         if(!po->id)
  1110.                 fprintf(stderr, "Object %p closed more than once!\n", po);
  1111.         po->id = 0;     /* Mark for eventual removal and destruction */
  1112. }
  1113.  
  1114.  
  1115. void pig_object_close_all(PIG_engine *pe)
  1116. {
  1117.         while(pe->objects)
  1118.                 close_object(pe->objects);
  1119. }
  1120.  
  1121.  
  1122. PIG_object *pig_object_find(PIG_object *start, int id)
  1123. {
  1124.         PIG_object *pob, *pof;
  1125.         if(start)
  1126.                 pob = pof = start;
  1127.         else
  1128.         {
  1129.                 pof = start->owner->objects;
  1130.                 while(pof)
  1131.                 {
  1132.                         if(pof->id == id)
  1133.                                 return pof;
  1134.                         pof = pof->next;
  1135.                 }
  1136.                 return NULL;
  1137.         }
  1138.         while(1)
  1139.         {
  1140.                 if(pob)
  1141.                 {
  1142.                         if(pob->id == id)
  1143.                                 return pob;
  1144.                         pob = pob->prev;
  1145.                 }
  1146.                 if(pof)
  1147.                 {
  1148.                         if(pof->id == id)
  1149.                                 return pof;
  1150.                         pof = pof->next;
  1151.                 }
  1152.                 else
  1153.                         if(!pob)
  1154.                                 return NULL;
  1155.         }
  1156. }
  1157.