/* entheh */

#include <math.h>
#include "main.h"
#include "tile.h"
#include "tilemap.h"
#include "sprite.h"
#include "collide.h"


void collide_vertex(TILEMAP *map,
                    SPRITE *sprite,
                    int xt, int yt,
                    TILE *tile,
                    TILE_SURFACES *hit,
                    float xvtx, float yvtx, float zvtx,
                    float *sx, float *sy, float *sz,
                    STC_CALLBACK callback) {

 float xd = sprite->x - xvtx;
 float yd = sprite->y - yvtx;
 float zd = sprite->z - zvtx;

 float d = xd*xd + yd*yd + zd*zd;

 if (d < sprite->r * sprite->r) {

  d = sqrt(d);

  xd /= d;
  yd /= d;
  zd /= d;

  d = sprite->r - d;

  sprite->x += xd*d;
  sprite->y += yd*d;
  sprite->z += zd*d;

  d = xd*sprite->xv + yd*sprite->yv + zd*sprite->zv;

  if (d < 0) {

   float e = 2*d;
   sprite->xv -= xd*e;
   sprite->yv -= yd*e;
   sprite->zv -= zd*e;

   if (callback) {
    *sx = xd*xd;
    *sy = yd*yd;
    *sz = zd*zd;

    (*callback)(map, sprite, xvtx, yvtx, zvtx, -d, xt, yt, tile, hit);

    *sx = 0;
    *sy = 0;
    *sz = 0;
   }
  }
 }
}



void collide_edge(TILEMAP *map,
                  SPRITE *sprite,
                  int xt, int yt,
                  TILE *tile,
                  TILE_SURFACES *hit,
                  float *u, float *v,
                  float *uv, float *vv,
                  float uedge, float vedge,
                  float *su, float *sv,
                  STC_CALLBACK callback) {

 float ud = *u - uedge;
 float vd = *v - vedge;

 float d = ud*ud + vd*vd;

 float xi, yi, zi;

 if (d < sprite->r * sprite->r) {

  d = sqrt(d);

  ud /= d;
  vd /= d;

  *u = uedge;
  *v = vedge;

  xi = sprite->x;
  yi = sprite->y;
  zi = sprite->z;

  *u += ud*sprite->r;
  *v += vd*sprite->r;

  d = ud*(*uv) + vd*(*vv);

  if (d < 0) {

   float e = 2*d;
   *uv -= ud*e;
   *vv -= vd*e;

   if (callback) {
    *su = ud*ud;
    *sv = vd*vd;

    (*callback)(map, sprite, xi, yi, zi, -d, xt, yt, tile, hit);

    *su = 0;
    *sv = 0;
   }
  }
 }
}



/* Note: we assume no sprite has a radius greater than 1. In truth, no sprite
   has a radius greater than 0.5, as required by the drawing code.
*/
void sprite_tilemap_collision(TILEMAP *tilemap,
                              SPRITE *sprite,
                              STC_CALLBACK callback) {

 TILE_SURFACES hit = {{0, 0, 0, 0, 0}};

 /* Order of consideration:

    - Sprite hits surface of tile over which it's hovering
    - Sprite hits face or edge of tile to each side, if overlapping
    - Sprite hits vertical edges of diagonal tiles, or top corners
 */

 /* Test for a collision with the tile under the sprite. */

 float xc, yc; /* the coordinates of the top-left corner of the tile */
 int x, y; /* the coordinates of the tile as integers */

 TILE *tile;

 if (!tilemap || !sprite)
	 return;

 xc = floor(sprite->x);
 yc = floor(sprite->y);

 x = (int)xc;
 y = (int)yc;

 /* Sanity checks; no consequences are processed here, but an edge-of-map
  * collision will be assured later.
  */
 if (x < 0) xc = x = (int)(sprite->x = 0);
 else if (x >= tilemap->w) xc = x = (int)(sprite->x = tilemap->w) - 1;
 if (y < 0) yc = y = (int)(sprite->y = 0);
 else if (y >= tilemap->h) yc = y = (int)(sprite->y = tilemap->h) - 1;

 tile = &tilemap->tile[y][x];

 if (sprite->z < tile->z + sprite->r) {
  sprite->z = tile->z + sprite->r;

  if (sprite->zv < 0) {
   sprite->zv = -sprite->zv;

   hit.s[TILE_SURFACE] = 1;

   if (callback) (*callback)(tilemap,
                             sprite,
                             sprite->x, sprite->y, tile->z,
                             sprite->zv,
                             x, y,
                             tile, &hit);

   hit.s[TILE_SURFACE] = 0;
  }
 }

 /* Test for collision with the tile to the left. */

 if (sprite->x < xc + sprite->r) {

  if (x > 0) {
   tile = &tilemap->tile[y][x-1];

   if (sprite->z < tile->z) {
    /* face collision */

    sprite->x = xc + sprite->r;

    if (sprite->xv < 0) {
     sprite->xv = -sprite->xv;

     hit.s[TILE_WALL_R] = 1;
     if (callback) (*callback)(tilemap,
                               sprite,
                               xc, sprite->y, sprite->z,
                               sprite->xv,
                               x-1, y,
                               tile, &hit);
     hit.s[TILE_WALL_R] = 0;
    }

   } else if (sprite->z < tile->z + sprite->r) {
    /* potential edge collision */

    collide_edge(tilemap,
                 sprite,
                 x-1, y,
                 tile,
                 &hit,
                 &sprite->x, &sprite->z,
                 &sprite->xv, &sprite->zv,
                 xc, tile->z,
                 &hit.s[TILE_WALL_R], &hit.s[TILE_SURFACE],
                 callback);
   }

  } else {

   /* Special case: edge of level */

   sprite->x = sprite->r;

   if (sprite->xv < 0) {
    sprite->xv = -sprite->xv;

    hit.s[TILE_WALL_L] = 1;
    if (callback) (*callback)(tilemap,
                              sprite,
                              0, sprite->y, sprite->z,
                              sprite->xv,
                              -1, 0,
                              &tilemap->tile[y][0], &hit);
    hit.s[TILE_WALL_L] = 0;
   }
  }
 }

 /* Test for collision with the tile to the right. */

 if (sprite->x > xc + 1.0 - sprite->r) {

  if (x < tilemap->w - 1) {
   tile = &tilemap->tile[y][x+1];

   if (sprite->z < tile->z) {
    /* face collision */

    sprite->x = xc + 1.0 - sprite->r;

    if (sprite->xv > 0) {
     float iv = sprite->xv;
     sprite->xv = -sprite->xv;

     hit.s[TILE_WALL_L] = 1;
     if (callback) (*callback)(tilemap,
                               sprite,
                               xc + 1.0, sprite->y, sprite->z,
                               iv,
                               x+1, y,
                               tile, &hit);
     hit.s[TILE_WALL_L] = 0;
    }

   } else if (sprite->z < tile->z + sprite->r) {
    /* potential edge collision */

    collide_edge(tilemap,
                 sprite,
                 x+1, y,
                 tile,
                 &hit,
                 &sprite->x, &sprite->z,
                 &sprite->xv, &sprite->zv,
                 xc + 1.0, tile->z,
                 &hit.s[TILE_WALL_L], &hit.s[TILE_SURFACE],
                 callback);
   }

  } else {

   /* Special case: edge of level */

   sprite->x = xc + 1.0 - sprite->r;

   if (sprite->xv > 0) {
    float iv = sprite->xv;
    sprite->xv = -sprite->xv;

    hit.s[TILE_WALL_R] = 1;
    if (callback) (*callback)(tilemap,
                              sprite,
                              xc + 1.0, sprite->y, sprite->z,
                              iv,
                              -1, 0,
                              &tilemap->tile[y][x], &hit);
    hit.s[TILE_WALL_R] = 0;
   }
  }
 }

 /* Test for collision with the tile to the top. */

 if (sprite->y < yc + sprite->r) {

  if (y > 0) {
   tile = &tilemap->tile[y-1][x];

   if (sprite->z < tile->z) {
    /* face collision */

    sprite->y = yc + sprite->r;

    if (sprite->yv < 0) {
     sprite->yv = -sprite->yv;

     hit.s[TILE_WALL_B] = 1;
     if (callback) (*callback)(tilemap,
                               sprite,
                               sprite->x, yc, sprite->z,
                               sprite->yv,
                               x, y-1,
                               tile, &hit);
     hit.s[TILE_WALL_B] = 0;
    }

   } else if (sprite->z < tile->z + sprite->r) {
    /* potential edge collision */

    collide_edge(tilemap,
                 sprite,
                 x, y-1,
                 tile,
                 &hit,
                 &sprite->y, &sprite->z,
                 &sprite->yv, &sprite->zv,
                 yc, tile->z,
                 &hit.s[TILE_WALL_B], &hit.s[TILE_SURFACE],
                 callback);
   }

  } else {

   /* Special case: edge of level */

   sprite->y = sprite->r;

   if (sprite->yv < 0) {
    sprite->yv = -sprite->yv;

    hit.s[TILE_WALL_T] = 1;
    if (callback) (*callback)(tilemap,
                              sprite,
                              sprite->x, 0, sprite->z,
                              sprite->yv,
                              -1, 0,
                              &tilemap->tile[0][x], &hit);
    hit.s[TILE_WALL_T] = 0;
   }
  }
 }

 /* Test for collision with the tile to the bottom. */

 if (sprite->y > yc + 1.0 - sprite->r) {

  if (y < tilemap->h - 1) {
   tile = &tilemap->tile[y+1][x];

   if (sprite->z < tile->z) {
    /* face collision */

    sprite->y = yc + 1.0 - sprite->r;

    if (sprite->yv > 0) {
     float iv = sprite->yv;
     sprite->yv = -sprite->yv;

     hit.s[TILE_WALL_T] = 1;
     if (callback) (*callback)(tilemap,
                               sprite,
                               sprite->x, yc + 1.0, sprite->z,
                               iv,
                               x, y+1,
                               tile, &hit);
     hit.s[TILE_WALL_T] = 0;
    }

   } else if (sprite->z < tile->z + sprite->r) {
    /* potential edge collision */

    collide_edge(tilemap,
                 sprite,
                 x, y+1,
                 tile,
                 &hit,
                 &sprite->y, &sprite->z,
                 &sprite->yv, &sprite->zv,
                 yc + 1.0, tile->z,
                 &hit.s[TILE_WALL_T], &hit.s[TILE_SURFACE],
                 callback);
   }

  } else {

   /* Special case: edge of level */

   sprite->y = yc + 1.0 - sprite->r;

   if (sprite->yv > 0) {
    float iv = sprite->yv;
    sprite->yv = -sprite->yv;

    hit.s[TILE_WALL_B] = 1;
    if (callback) (*callback)(tilemap,
                              sprite,
                              sprite->x, yc + 1.0, sprite->z,
                              iv,
                              -1, 0,
                              &tilemap->tile[y][x], &hit);
    hit.s[TILE_WALL_B] = 0;
   }
  }
 }

 if (y > 0) {

  /* Test for collision with the tile to the top left. */

  if (x > 0) {
   tile = &tilemap->tile[y-1][x-1];

   if (sprite->z < tile->z) {
    /* potential edge collision */

    collide_edge(tilemap,
                 sprite,
                 x-1, y-1,
                 tile,
                 &hit,
                 &sprite->x, &sprite->y,
                 &sprite->xv, &sprite->yv,
                 xc, yc,
                 &hit.s[TILE_WALL_R], &hit.s[TILE_WALL_B],
                 callback);

   } else if (sprite->z < tile->z + sprite->r) {
    /* potential vertex collision */

    collide_vertex(tilemap,
                   sprite,
                   x-1, y-1,
                   tile,
                   &hit,
                   xc, yc, tile->z,
                   &hit.s[TILE_WALL_R], &hit.s[TILE_WALL_B], &hit.s[TILE_SURFACE],
                   callback);
   }
  }

  /* Test for collision with the tile to the top right. */

  if (x < tilemap->w - 1) {
   tile = &tilemap->tile[y-1][x+1];

   if (sprite->z < tile->z) {
    /* potential edge collision */

    collide_edge(tilemap,
                 sprite,
                 x+1, y-1,
                 tile,
                 &hit,
                 &sprite->x, &sprite->y,
                 &sprite->xv, &sprite->yv,
                 xc + 1.0, yc,
                 &hit.s[TILE_WALL_L], &hit.s[TILE_WALL_B],
                 callback);

   } else if (sprite->z < tile->z + sprite->r) {
    /* potential vertex collision */

    collide_vertex(tilemap,
                   sprite,
                   x+1, y-1,
                   tile,
                   &hit,
                   xc + 1.0, yc, tile->z,
                   &hit.s[TILE_WALL_R], &hit.s[TILE_WALL_B], &hit.s[TILE_SURFACE],
                   callback);
   }
  }

 }

 if (y < tilemap->h - 1) {

  /* Test for collision with the tile to the bottom left. */

  if (x > 0) {
   tile = &tilemap->tile[y+1][x-1];

   if (sprite->z < tile->z) {
    /* potential edge collision */

    collide_edge(tilemap,
                 sprite,
                 x-1, y+1,
                 tile,
                 &hit,
                 &sprite->x, &sprite->y,
                 &sprite->xv, &sprite->yv,
                 xc, yc + 1.0,
                 &hit.s[TILE_WALL_R], &hit.s[TILE_WALL_T],
                 callback);

   } else if (sprite->z < tile->z + sprite->r) {
    /* potential vertex collision */

    collide_vertex(tilemap,
                   sprite,
                   x-1, y+1,
                   tile,
                   &hit,
                   xc, yc + 1.0, tile->z,
                   &hit.s[TILE_WALL_R], &hit.s[TILE_WALL_T], &hit.s[TILE_SURFACE],
                   callback);
   }
  }

  /* Test for collision with the tile to the top right. */

  if (x < tilemap->w - 1) {
   tile = &tilemap->tile[y+1][x+1];

   if (sprite->z < tile->z) {
    /* potential edge collision */

    collide_edge(tilemap,
                 sprite,
                 x+1, y+1,
                 tile,
                 &hit,
                 &sprite->x, &sprite->y,
                 &sprite->xv, &sprite->yv,
                 xc + 1.0, yc + 1.0,
                 &hit.s[TILE_WALL_L], &hit.s[TILE_WALL_T],
                 callback);

   } else if (sprite->z < tile->z + sprite->r) {
    /* potential vertex collision */

    collide_vertex(tilemap,
                   sprite,
                   x+1, y+1,
                   tile,
                   &hit,
                   xc + 1.0, yc + 1.0, tile->z,
                   &hit.s[TILE_WALL_L], &hit.s[TILE_WALL_T], &hit.s[TILE_SURFACE],
                   callback);
   }
  }

 }

}
