/*
Copyright (C) 2003 - Derek John Evans 

This file is part of Yeti3D Portable Engine

Yeti3D 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.

Original Source: 2003 - Derek J. Evans <derek@theteahouse.com.au>
Prepared for public release: 10/24/2003 - Derek J. Evans <derek@theteahouse.com.au>
*/

/*
** Name: Yeti3D
** Desc: Portable GameBoy Advanced 3D Engine
** Auth: Derek J. Evans <derek@theteahouse.com.au>
**
** Copyright (C) 2003 Derek J. Evans. All Rights Reserved.
$**
** YY  YY EEEEEE TTTTTT IIIIII 33333  DDDDD
** YY  YY EE       TT     II       33 DD  DD
**  YYYY  EEEE     TT     II     333  DD  DD
**   YY   EE       TT     II       33 DD  DD
**   YY   EEEEEE   TT   IIIIII 33333  DDDDD
*/

#include "yeti.h"
#include "game.h"

/******************************************************************************/

/*
** Name: palette_overbright
** Desc: Converts a standard palette to a overbright palette. Use this on
**       platforms that bypass the LUA. 
*/
void palette_overbright(palette_t dst, palette_t src, int brightness)
{
  int i;
  
  for (i = 0; i < 256; i++)
  {
    int r = src[i][0] * brightness / FIXONE;
    int g = src[i][1] * brightness / FIXONE;
    int b = src[i][2] * brightness / FIXONE;

    dst[i][0] = CLAMP(r, 0, 255);
    dst[i][1] = CLAMP(g, 0, 255);
    dst[i][2] = CLAMP(b, 0, 255);    
  }
}

/*
** Name: rgb_convert
** Desc: Converts a rgb color to another format.
*/
int rgb_convert(int color, int rmask, int gmask, int bmask)
{
  return
    ((RGB_RED  (color) * rmask / 31) & rmask) |
    ((RGB_GREEN(color) * gmask / 31) & gmask) |
    ((RGB_BLUE (color) * bmask / 31) & bmask) ;
    /* Test for Saturn (unused now) */
    /*int r, g, b;
    r = ((RGB_RED  (color) * rmask / 31) & rmask) ;
    g = ((RGB_GREEN(color) * gmask / 31) & gmask) ;
    b = ((RGB_BLUE (color) * bmask / 31) & bmask) ;
    return (((r)&0x1F)|((g)&0x1F)<<5|((b)&0x1F)<<10|0x8000);*/
}

/*
** Name: viewport_to_video
** Desc: The Yeti3D uses 5:5:5 colour, so if your platform uses a different
**       format, then one solution is to convert the pixels when blitting to
**       the video buffer.
*/
#ifdef YETI_VIDEO_LUT
#ifndef YETI_NO_FRAMEBUFFER
void viewport_to_video(
  rgb555_t* video, int pitch,
  viewport_t* vp,
  int rmask, int gmask, int bmask)
{
  int i, y;

  if (!vp->video_lut_filled)
  {
    vp->video_lut_filled = TRUE;

    for (i = RGB_MAX; i--;)
    {
      vp->video_lut[i] = rgb_convert(i, rmask, gmask, bmask);
    }
  }
  for (y = 0; y < YETI_VIEWPORT_HEIGHT; y++)
  {
    rgb555_t* fb = video;
    rgb555_t* tb= vp->back->pixels[y];
    i = YETI_VIEWPORT_WIDTH;
#define AFFINE(I) {fb[I] = vp->video_lut[*tb++];}
    AFFINE_LOOP
#undef AFFINE
    video = (rgb555_t*)((int)video + pitch);
  }
}
#endif // YETI_NO_FRAMEBUFFER
#endif

/******************************************************************************/

/*
** Name: cell_init
** Desc: Initialize a yeti cell to its defaults.
*/
void cell_init(cell_t* cell, int issolid)
{
  CLEARMEM(cell);
  
  if (issolid)
  {
    cell->top = i2f(0);
    cell->bot = i2f(0);
  }
  else
  {
    cell->top = i2f(4);
    cell->bot = i2f(0);
  }
#ifdef __YETI_EDITOR__
  cell->tos = cell->top;
  cell->bos = cell->bot;
#endif
  cell->lit = YETI_LIGHT_MAX;
  cell->ttx = 0; //7;
  cell->wtx = 1; //0;
  cell->btx = 0; //28;
}

/*
** Name: yeti_entity
** Desc: Creates a new entity and returns a entity pointer.
*/
entity_t* yeti_entity(yeti_t* yeti, int x, int y, int z, entity_behaviour_t behaviour)
{
  entity_t* e = &yeti->entities[yeti->nentities++];

  CLEARMEM(e);
  
  e->x = x;
  e->y = y;
  e->z = z;
  e->ontick = behaviour;
  e->radius = i2fdiv2(1);
  e->life = 100;
  e->yeti = yeti;

  return e;
}

/*
** Name: yeti_light
** Desc: Renders a spot light at a given cell location.
*/
void yeti_light(yeti_t* yeti, int lightx, int lighty)
{
  int i, lit, xhit, yhit;

  for (i = 0; i < 2048; i += 8)
  {
    int x = lightx, xx = fixsin(i) >> 1;
    int y = lighty, yy = fixcos(i) >> 1;

    for (lit = 63; lit > 0; )
    {
      x += xx; if ((xhit = CELL_IS_SOLID(&yeti->cells[f2i(y)][f2i(x)])) != 0) xx = -xx;
      y += yy; if ((yhit = CELL_IS_SOLID(&yeti->cells[f2i(y)][f2i(x)])) != 0) yy = -yy;

      yeti->cells[f2i(y + FIXHALF)][f2i(x + FIXHALF)].lit += lit;

      if (xhit || yhit) lit >>= 2;
    }
  }
}

/*
** Name: entity_motion
** Desc: Simple map to entity motion & collision.
*/
void entity_motion(entity_t* e)
{
  yeti_t* yeti = (yeti_t*)e->yeti;

//yeti_logout("entity_motion, e = 0x%08X", e);
  e->x += e->xx; e->y += e->yy; e->z += e->zz;
  e->r += e->rr; e->t += e->tt; e->p += e->pp;

  if (e->radius)
  {
    int x, y;
    cell_t* cell;
//yeti_logout("entity_motion, e->radius = 0x%08X", e->radius);

#define IS_COLLISION ((e->z > cell->top) || (e->z < cell->bot))

    cell = &yeti->cells[CELL_X_CLAMP(f2i(e->y))][CELL_Y_CLAMP(f2i(x = e->x - e->radius))];

    if (IS_COLLISION)
    {
      e->x += FIXONE - (x & FIXCEIL);
      e->xx = 0;
    }

    cell = &yeti->cells[CELL_X_CLAMP(f2i(e->y))][CELL_Y_CLAMP(f2i(x = e->x + e->radius))];

    if (IS_COLLISION)
    {
      e->x -= (x & FIXCEIL);
      e->xx = 0;
    }

    cell = &yeti->cells[CELL_X_CLAMP(f2i(y = e->y - e->radius))][CELL_Y_CLAMP(f2i(e->x))];

//yeti_logout("cell[%d][%d] = 0x%08X", CELL_X_CLAMP(f2i(e->y - e->radius)), CELL_Y_CLAMP(f2i(e->x)), cell);
    if (IS_COLLISION)
    {
      e->y += FIXONE - (y & FIXCEIL);
      e->yy = 0;
    }

    cell = &yeti->cells[CELL_X_CLAMP(f2i(y = e->y + e->radius))][CELL_Y_CLAMP(f2i(e->x))];

    if (IS_COLLISION)
    {
      e->y -= (y & FIXCEIL);
      e->yy = 0;
    }
  }
}

/******************************************************************************/

void yeti_clear_entities(yeti_t* yeti)
{
  int i;

  yeti->nentities = 0;

  yeti->camera = yeti_entity(yeti,
    i2fdiv2(yeti->map_width),
    i2fdiv2(yeti->map_height),
    i2f(0), 0);

  for (i = 0; i < YETI_BULLET_MAX; i++)
  {
    yeti->bullets[i] = yeti_entity(yeti, 0, 0, 0, 0);
  }
yeti_logout("yeti->nentities = %d", yeti->nentities);
}

/*
** Name: yeti_load_cell
** Desc: Loads a cell from a ROM based structure.
*/
void yeti_load_cell(cell_t* dst, rom_cell_t* src)
{
  dst->swi = src->swi;
  dst->ent = src->ent;
  dst->top = SWAP_SHORT(src->top);
  dst->mid = SWAP_SHORT(src->mid);
  dst->bot = SWAP_SHORT(src->bot);
  dst->wtx = src->wtx;
  dst->ttx = src->ttx;
  dst->btx = src->btx;
  dst->lit = SWAP_SHORT(src->lit);
}

/*
** Name: yeti_save_cell
** Desc: Saves a cell from a ROM based structure.
*/
void yeti_save_cell(cell_t* src, rom_cell_t* dst)
{
  dst->swi = src->swi;
  dst->ent = src->ent;
  dst->top = SWAP_SHORT(src->top);
  dst->mid = SWAP_SHORT(src->mid);
  dst->bot = SWAP_SHORT(src->bot);
  dst->wtx = src->wtx;
  dst->ttx = src->ttx;
  dst->btx = src->btx;
  dst->lit = SWAP_SHORT(src->lit);
}




/*
** Name: yeti_set_texture_params
** Desc: Set texture parameters.
*/
void yeti_set_texture_params(tex_entry_t* tex, u8* tex_data, u32 offset, u16 width, u16 height)
{
    tex->offset = offset;
#ifdef __SEGA_SATURN__
    tex->vdp1_offs = (0x40000 + (offset & 0xFFFFF)) >> 3;
#endif
    tex->width  = width;
    tex->height = height;
}


/******************************************************************************/

/*
** Name: isqrt
** Desc: Integer square root. Take the square root of an integer.
*/
int isqrt(int value)
{
  int root = 0;

#define STEP(shift) \
    if((0x40000000 >> shift) + root <= value)          \
    {                                                   \
        value -= (0x40000000 >> shift) + root;          \
        root = (root >> 1) | (0x40000000 >> shift);     \
    }                                                   \
    else                                                \
    {                                                   \
        root >>= 1;                                     \
    }

  STEP( 0); STEP( 2); STEP( 4); STEP( 6);
  STEP( 8); STEP(10); STEP(12); STEP(14);
  STEP(16); STEP(18); STEP(20); STEP(22);
  STEP(24); STEP(26); STEP(28); STEP(30);

  // round to the nearest integer, cuts max error in half

  if (root < value) root++;

  return root;
}

/******************************************************************************/
/*
** ROM<->RAM load/save functions, reset functions, etc.
**
*/




int lua_rgb (int r, int g, int b)
{
    int r1 = (MIN (r, 255) * 31) / 255;
    int g1 = (MIN (g, 255) * 31) / 255;
    int b1 = (MIN (b, 255) * 31) / 255;

//return (r1 << 10) + (g1 << 5) + b1;
//return ( ( ( r1 ) << 11 )  |  ( ( g1 ) << 6 )  |  ( ( b1 ) << 1 ) );
    return RGB_SET(r1, g1, b1);
}



/*
** Name: yeti_reset
** Desc: Reset yeti data. Needed in the case you don't have yeti ROM data yet.
** Note: cells_rows * cells_cols cell data are required in `cells' parameter.
** Note: tex_len bytes are required in `tex_data' parameter.
** Note: After calling this function, you are ready to play.
*/
void yeti_reset(yeti_t* yeti, cell_t* cells_data, int cells_rows, int cells_cols, unsigned char* tex_data, unsigned long tex_len, framebuffer_t* front, lua_t lua)
{
    int id, i, x, y, o;
    u16 colors[4];
    u16* tex = (u16*)tex_data;

    /* Reset yeti internal data. */
    yeti_memset((void*)yeti, 0, sizeof(yeti_t));

    /* Init 8bits palette stuff. */
#ifdef YETI_TEX_PALETTE
    /**
    * Palette and lua data is the same as the one in original yeti3d engine.
    * Note: It is possible to set the palette colors to other values
    *       here, however you need to change lua data too !
    **/
    
    /* Set yeti pointers to local data. */
    yeti->textures = _textures;
    yeti->palette  = _palette;
    
    /* Reset 8bit textures data. */
    yeti_memset((void*)yeti->textures, 0, YETI_TEXTURE_MAX * sizeof(texture_t));
    
    /* Use palette and lua data as-is, so don't reset them. */
    //from TECH32
//    for(a=0;a<64;a++)
//    {
//      for(i=0;i<256;i++)
//      {
//        lua[a][i]=lua_rgb(palette[i][0]*a/16,palette[i][1]*a/16,palette[i][2]*a/16);
//      }
//    }
#endif

    /* Reset RGB555 textures data. */
yeti_logout("[INIT]Textures ...", 0);
    yeti_memset((void*)tex_data, 0, tex_len);

    /* Note: on Saturn, texture width must be a multiple of 8, so default texture size is 8 x 8. */
    colors[0] = RGB_SET( 15,  0,  0); yeti_logout("colors[0] = 0x%04X", colors[0]);
    colors[1] = RGB_SET(  0, 15, 15); yeti_logout("colors[1] = 0x%04X", colors[1]);
    colors[2] = RGB_SET(  0, 15,  0); yeti_logout("colors[2] = 0x%04X", colors[2]);
    colors[3] = RGB_SET(  0,  0, 15); yeti_logout("colors[3] = 0x%04X", colors[3]);
    for (y = 0; y < 8; y++)
    {
        for (x = 0; x < 8; x++)
        {
            i = y*8 + x;
            o = (x<4 ? (y<4 ? 0 : 1 ) : (y<4 ? 1 : 0 ));
            /* Set ground texture. */
            tex[   i] = colors[  o];
            /* Set wall texture. */
            tex[64+i] = colors[2+o];
        }
    }

    /* Init textures table. */
yeti_logout("[INIT]Textures table ...", 0);
    for(id=0; id<YETI_TEXTURE_MAX; id++)
    {
      yeti_set_texture_params(yeti->tex_table + id, yeti->tex_data, 
                  (id%2 == 0 ? 0 : 64) * sizeof(u16) /*offset*/, 
                  8 /*width*/, 
                  8 /*height*/);
    }

    /* Init textures data. */
    yeti->tex_data     = tex_data;
    yeti->tex_data_len = tex_len;
    yeti->tex_len      = 8*8*2 * sizeof(u16); /* 256 bytes needed for all the textures. */
    yeti_textures_init(yeti, tex_data, tex_len);

    /* Init cells sizes. */
    yeti->cells_rows = cells_rows;
    yeti->cells_cols = cells_cols;
    /* Init map sizes to maximum allowed sizes. */
    yeti->map_width  = yeti->cells_cols;
    yeti->map_height = yeti->cells_rows;

    /* Init map pointers. */
yeti_logout("[INIT]Map pointers ...", 0);
    yeti->cells_data = cells_data;
    for(id=0; id<YETI_MAP_HEIGHT; id++) { yeti->cells[id] = cells_data; }
    for(id=0; id<yeti->cells_rows; id++)
    {
        yeti->cells[id] = cells_data + id * yeti->cells_cols;
    }

    /*
    ** Name: yeti_reset_map
    ** Desc: Clears a yeti map to its default settings.
    */
    //void yeti_reset_map(yeti_t* yeti)
    //{
    //  int x, y;
yeti_logout("[INIT]Reset map ...", 0);
    for (y = 0; y < yeti->map_height; y++)
    {
        for (x = 0; x < yeti->map_width; x++)
        {
            cell_t* cell = &yeti->cells[y][x];
            cell_init(cell,
                x == 0 || y == 0 ||
                (x == yeti->map_width  - 1) ||
                (y == yeti->map_height - 1));

            if (!((x+8)&15) && !((y+8)&15))
            {
                cell->swi |= CELL_SWI_LIGHT;
            }
        } 
    }
    //}



    /* Set default lighting. */
yeti_logout("[INIT]Default lighting ...", 0);
    yeti_default_lighting(yeti);


    /*
    ** Name: yeti_init
    ** Desc: Setup a default map and position the camera.
    */
    //void yeti_init(yeti_t* yeti, framebuffer_t* front, framebuffer_t* back, texture_t* textures, palette_t palette, lua_t lua)
    for (i = 0; i < YETI_TEXTURE_MAX; i++)
    {
        yeti->surfaces[i].xsize = 6;
        yeti->surfaces[i].ysize = 6;
    }

    yeti->surfaces[YETI_TEXTURE_SKY].xsize = 5;
    yeti->surfaces[YETI_TEXTURE_SKY].ysize = 5;

    yeti->surfaces[0].xsize = 4;
    yeti->surfaces[0].ysize = 4;
    yeti->surfaces[2].xsize = 5;
    yeti->surfaces[2].ysize = 5;

    //yeti_reset_map(yeti);
  
#ifndef YETI_NO_FRAMEBUFFER
      yeti->viewport.front = front;
      yeti->viewport.back  = front;
#endif // YETI_NO_FRAMEBUFFER

//#ifdef YETI_TEX_PALETTE
    //  yeti->textures    = textures;
    //  yeti->palette     = palette;
//#endif
    yeti->lighting[0] = lua;
    yeti->lighting[1] = lua;
    yeti->lighting[2] = lua;
    yeti->lighting[3] = lua;


    //void yeti_load_map(yeti_t* yeti, rom_map_t* src)
yeti_logout("[INIT]Clear entities ...", 0);
    yeti_clear_entities(yeti);

    yeti->camera->x = i2f(yeti->map_width  / 2) + FIXHALF;
    yeti->camera->y = i2f(yeti->map_height / 2) + FIXHALF;
yeti_logout("[INIT]Camera ...", 0);
    for (y = 0; y < yeti->map_height; y++)
    {
        for (x = 0; x < yeti->map_width; x++)
        {
            if (yeti->cells[y][x].ent == 1)
            {
                yeti->camera->x = i2f(x) + FIXHALF;
                yeti->camera->y = i2f(y) + FIXHALF;
            }
        }
    }
    yeti->camera->t = 0;
    yeti->camera->r = 0;
    yeti->camera->p = i2f(256);

    /* Init game. */
yeti_logout("[INIT]Game Init ...", 0);
    game_init(yeti);
yeti_logout("[INIT]END !!!", 0);
}







/*
** Name: yeti_header_init
** Desc: Init header data from a ROM based structure.
** Note: After init, you don't need the rom_header_t data anymore.
** Note: In the case header is null, dummy values are set.
*/
void yeti_header_init(yeti_t* yeti, rom_header_t* header)
{
  int id;
#ifdef YETI_TEX_PALETTE
  int i, j, k;
#endif

//  CLEARMEM(yeti);

  /*--- Map data-related. ---*/
  yeti->map_offset = SWAP_LONG(header->map_offset);
yeti_logout("yeti->map_offset = %d", yeti->map_offset);
  yeti->map_len    = SWAP_LONG(header->map_len);
yeti_logout("yeti->map_len = %d", yeti->map_len);
  yeti->map_width  = SWAP_SHORT(header->map_width);
yeti_logout("yeti->map_width = %d", yeti->map_width);
  yeti->map_height = SWAP_SHORT(header->map_height);
yeti_logout("yeti->map_height = %d", yeti->map_height);

  /*--- Textures data-related. ---*/
  yeti->tex_offset = SWAP_LONG(header->tex_offset);
yeti_logout("yeti->tex_offset = %d", yeti->tex_offset);
  yeti->tex_len    = SWAP_LONG(header->tex_len);
yeti_logout("yeti->tex_len = %d", yeti->tex_len);

  /* Set texture parameters. */
  for(id=0; id<YETI_TEXTURE_MAX; id++)
  {
    yeti_set_texture_params(yeti->tex_table + id, yeti->tex_data, 
                SWAP_LONG(header->tex_table[id].offset), 
                SWAP_SHORT(header->tex_table[id].width), 
                SWAP_SHORT(header->tex_table[id].height));
  }
}




/*
** Look for the color in the palette that best fits the input color.
*/
#ifdef YETI_TEX_PALETTE
int palette_lookup(yeti_t* yeti, unsigned char r, unsigned char g, unsigned char b)
{
    int i, d, e, di = 0, dmin = 3 * 256 * 256;
    for(i=0; i<256; i++)
    {
        e = yeti->palette[i][0] - r; d  = e * e;
        if(d >= dmin) continue;
        e = yeti->palette[i][1] - g; d += e * e;
        if(d >= dmin) continue;
        e = yeti->palette[i][2] - b; d += e * e;
        if(d < dmin)
        {
            dmin = d;
            di = i;
            if(dmin <= 27) return di;
        }
    }
    return di;
}
#endif // YETI_TEX_PALETTE





/*
** Name: yeti_textures_init
** Desc: Init texture parameters from texture data in (video) RAM.
** Note: As pointers to video RAM are used, you NEED to call this
**       function directly after texture data have changed.
*/
void yeti_textures_init(yeti_t* yeti, unsigned char* tex_data, unsigned long tex_len)
{
  tex_entry_t* tex = 0;
  int id;
#ifdef YETI_TEX_PALETTE
  int i, j, x, y;
  u16* ptr;
  u16 pix;
  color_t rgb;
#endif

yeti_logout("yeti_textures_init START", 0);

  /* Set texture parameters. */
  yeti->tex_data = tex_data;
  for(id=0; id<YETI_TEXTURE_MAX; id++)
  {
    tex = yeti->tex_table + id;

    //yeti_set_texture_params(tex, yeti->tex_data, 
    //            tex->offset, 
    //            tex->width, 
    //            tex->height);

#ifdef YETI_TEX_PALETTE
    /* Convert current texture data from RGB555, free size to 8 bits paletted data, 64x64. */
//yeti_logout("[%d]tex->width = %d, tex->height = %d", id, tex->width, tex->height);
    if((tex->width != 0) && (tex->height != 0))
    {
      ptr = (u16*)(yeti->tex_data + tex->offset);

      /* Resize texture (nearest neighboor algorithm). */
      for(i=0; i<64; i++)
      {
        x = (i * tex->width ) / 64;
        for(j=0; j<64; j++)
        {
          y = (j * tex->height) / 64;
          pix = ptr[ y*tex->width + x ];
          /* Extract RGB values. */
          rgb[0] = RGB_RED(pix)   << 3; // R
          rgb[1] = RGB_GREEN(pix) << 3; // G
          rgb[2] = RGB_BLUE(pix)  << 3; // B
          /* Convert RGB color to 8 bits palette value. */
          yeti->textures[id][j][i] = palette_lookup(yeti, rgb[0], rgb[1], rgb[2]);
        }
      }
    }
#endif // YETI_TEX_PALETTE
  }


#ifdef YETI_TEX_PALETTE
// Execution time of the textures conversion stuff is very slow, even on a recent PC ...
// On my PC: 2.86 seconds
yeti_logout("yeti->textures[4][0][0] = R%d G%d B%d", 
yeti->palette[yeti->textures[4][0][0]][0], 
yeti->palette[yeti->textures[4][0][0]][1], 
yeti->palette[yeti->textures[4][0][0]][2]);
#endif // YETI_TEX_PALETTE

}


/*
** Name: yeti_map_line_init
** Desc: Init a single line of map data from a ROM based structure.
** Note: In order to face the case where ROM data is bigger than
**       read buffer in RAM, cells are loaded line per line.
**       start cell ID, and cell count of data to process.
** Note: After init, you don't need the rom_cell_t data anymore.
*/
void yeti_map_line_init(yeti_t* yeti, rom_cell_t* src, int line, cell_t* dst)
{
    int i;
    yeti->cells[line] = dst;
    for(i=0; i<yeti->map_width; i++)
    {
        yeti_load_cell(dst + i, src + i);
    }
//yeti_logout("MAP LINE INIT (line %d, width %d, ptr = 0x%08X)", line, yeti->map_width, yeti->cells[line]);
}

/*
** Name: yeti_map_init
** Desc: Init map data from a ROM based structure.
** Note: After init, you don't need the rom_cell_t data anymore.
*/
void yeti_map_init(yeti_t* yeti, rom_cell_t* src)
{
    int i, offset;
    for(i=0; i<yeti->map_height; i++)
    {
        offset = i * yeti->map_width;
        yeti_map_line_init(yeti, src + offset, i, yeti->cells_data + offset);
    }
}




/*
** Name: yeti_load_rom
** Desc: Init all the stuff from a ROM data.
*/
void yeti_load_rom(yeti_t* yeti, yeti_rom_t* rom)
{
/**
 * What is done in this function:
 *
 * 0) Call yeti_reset function in order to perform data initialisations.
 *
 * 1) Read ROM header data (rom_header_t) from ROM to temporary buffer.
 * 2) Convert ROM header data from buffer to yeti with yeti_header_init function.
 *
 * 3) Read texture data from ROM to video RAM.
 *     -> Their location in ROM are given in yeti->tex_offset and yeti->tex_len.
 * 4) Init texture data into yeti data with yeti_textures_init function.
 *
 * 5) Read ROM map data from ROM to temporary buffer.
 *     -> Their location in ROM are given in yeti->map_offset and yeti->map_len.
 * 6) Convert ROM map data from buffer to ROM map data with yeti_map_init function.
 *
 * 7) Call game_init function in order to perform final data initialisations.
**/

    int x, y;

    /* Read header data. */
yeti_logout("yeti_header_init start", 0);
    yeti_header_init(yeti, (rom_header_t*)rom);
yeti_logout("yeti_header_init end", 0);

    /* Copy textures to RAM. */
yeti_logout("CopyTEX: yeti->tex_data   = 0x%08X", yeti->tex_data);
yeti_logout("CopyTEX: ROM              = 0x%08X", rom);
yeti_logout("CopyTEX: yeti->tex_offset = 0x%08X", yeti->tex_offset);
yeti_logout("CopyTEX: yeti->tex_len    = 0x%08X", yeti->tex_len);

    /* Init textures. */
    yeti_memcpy((void*)(yeti->tex_data), (void*)((u8*)(rom) + yeti->tex_offset), yeti->tex_len);
yeti_logout("yeti_textures_init start", 0);
    yeti_textures_init(yeti, yeti->tex_data, yeti->tex_len);
yeti_logout("yeti_textures_init end", 0);

    /* Copy and init cell data. */
yeti_logout("yeti_map_init start", 0);
    yeti_map_init(yeti, (rom_cell_t*)((u8*)(rom) + yeti->map_offset));
yeti_logout("yeti_map_init end", 0);


    /* Init camera. */
yeti_logout("Init camera ...", 0);
    yeti_clear_entities(yeti);

    yeti->camera->x = i2f(yeti->map_width  / 2) + FIXHALF;
    yeti->camera->y = i2f(yeti->map_height / 2) + FIXHALF;
    for (y = 0; y < yeti->map_height; y++)
    {
        for (x = 0; x < yeti->map_width; x++)
        {
            if (yeti->cells[y][x].ent == 1)
            {
                yeti->camera->x = i2f(x) + FIXHALF;
                yeti->camera->y = i2f(y) + FIXHALF;
            }
        }
    }
    yeti->camera->t = 0;
    yeti->camera->r = 0;
    yeti->camera->p = i2f(256);


    /* Init game. */
yeti_logout("game_init start", 0);
    game_init(yeti);
yeti_logout("game_init end", 0);



    //// Display yeti data contents
    //yeti_logout("--------------------------------", 0);
    //for(i=0; i<YETI_MAP_HEIGHT; i++)
    //{
    //    yeti_logout("yeti->cells[%d] = 0x%08X", i, yeti->cells[i]);
    //}
    //yeti_logout("", 0);
    //yeti_logout("yeti->map_height = %d", yeti->map_height);
    //yeti_logout("yeti->map_width = %d", yeti->map_width);
    //for (y = 0; y < yeti->map_height; y++)
    //{
    //    for (x = 0; x < yeti->map_width; x++)
    //    {
    //    yeti_logout(" yeti->cells[%d][%d].ttx = %d", x, y, yeti->cells[y][x].ttx);
    //    }
    //}
    //
    //for(i=0; i<YETI_TEXTURE_MAX; i++)
    //{
    //    yeti_logout("  yeti->tex_table[%d] = 0x%08X", i, yeti->tex_table[i]);
    //}

}

/*
** Name: yeti_load_rom
** Desc: Init all the stuff from a ROM data.
** Returns: Size (in bytes) of the ROM data.
*/
u32 yeti_save_rom(yeti_t* yeti, yeti_rom_t* rom)
{
/**
 * What is done in this function:
 *
 * 1) Save header data to ROM data.
 *
 * 2) Save map data to ROM data.
 *
 * 3) Save textures data to ROM data.
 *
 * 4) Return ROM length.
 *
**/

    unsigned long len = 0;
    rom_header_t* header = (rom_header_t*)rom;
    unsigned char* ptr = (unsigned char*)rom;
    unsigned long map_offset, map_len;
    unsigned long tex_offset, tex_len;
    int id, x, y;


    //memset((void*)rom, 0, sizeof(rom_header_t));
    /*--- Map data-related. ---*/
    //header->map_offset = SWAP_LONG(yeti->map_offset); /* Computed in the map data section */
    //header->map_len    = SWAP_LONG(yeti->map_len);    /* Computed in the map data section */
    header->map_width  = SWAP_SHORT(yeti->map_width);
    header->map_height = SWAP_SHORT(yeti->map_height);

    /*--- Textures data-related. ---*/
    //header->tex_offset = SWAP_LONG(yeti->tex_offset); /* Computed in the textures data section */
    //header->tex_len    = SWAP_LONG(yeti->tex_len);    /* Computed in the textures data section */

    /* Set texture parameters. */
    for(id=0; id<YETI_TEXTURE_MAX; id++)
    {
        header->tex_table[id].offset = SWAP_LONG(yeti->tex_table[id].offset);
        header->tex_table[id].width  = SWAP_LONG(yeti->tex_table[id].width);
        header->tex_table[id].height = SWAP_LONG(yeti->tex_table[id].height);
    }
    /* Update ROM lenghth */
    len += sizeof(rom_header_t);


    /*********************************************************/
    /* Convert map data. (crop unused map data when saving). */
    map_offset = len;
    map_len    = yeti->map_width * yeti->map_height * sizeof(rom_cell_t);
    header->map_offset = SWAP_LONG(map_offset);
    header->map_len    = SWAP_LONG(map_len);
    for (y = 0; y < yeti->map_height; y++)
    {
        for (x = 0; x < yeti->map_width; x++)
        {
            cell_t*     ycell = &yeti->cells[y][x];
            rom_cell_t* rcell = (rom_cell_t*)(ptr + map_offset + (y * yeti->map_width + x) * sizeof(rom_cell_t));
            yeti_save_cell(ycell, rcell);
        } 
    }
    /* Update ROM lenghth */
    len += map_len;


    /********************************/
    /* Convert textures data as-is. */
    tex_offset = len;
    tex_len    = yeti->tex_len;
    header->tex_offset = SWAP_LONG(tex_offset);
    header->tex_len    = SWAP_LONG(tex_len);
    memcpy((void*)(ptr + tex_offset), (void*)(yeti->tex_data), tex_len);
    /* Update ROM length */
    len += tex_len;

    return len;
}



/******************************************************************************/


void yeti_tick(yeti_t* yeti)
{
  int i;

  yeti->tick++;

  for (i = 0; i < yeti->nentities; i++)
  {
    entity_t* e = &yeti->entities[i];
//yeti_logout("TICK e=0x%08X", e);
//yeti_logout("TICK e->ontick=0x%08X", e->ontick);
    if (e->ontick) e->ontick(e);
  }

  for (i = 0; i < yeti->nentities; i++)
  {
    entity_t* e1 = &yeti->entities[i];

    entity_motion(e1);

    if (e1->radius)
    {
      int j;
      
      for (j = i + 1; j < yeti->nentities; j++)
      {
        entity_t* e2 = &yeti->entities[j];

        if (e2->radius)
        {
          int size = e1->radius + e2->radius;
          int x = e1->x - e2->x;
          int y = e1->y - e2->y;
          int z = e1->z - e2->z;

          if (
            x < size && x > -size &&
            y < size && y > -size &&
            z < size && z > -size )
          {
            if (e1->onhit) e1->onhit(e1, e2);
            if (e2->onhit) e2->onhit(e2, e1);

            if (!(e1->swi & ENTITY_SWI_NO_COLLISION_RESPONSE))
            {
              if (x > 0) {e1->x += ( size - x) >> 1;}
              if (x < 0) {e1->x += (-size - x) >> 1;}
              if (y > 0) {e1->y += ( size - y) >> 1;}
              if (y < 0) {e1->y += (-size - y) >> 1;}
            }
            if (!(e2->swi & ENTITY_SWI_NO_COLLISION_RESPONSE))
            {
              if (x > 0) {e2->x -= ( size - x) >> 1;}
              if (x < 0) {e2->x -= (-size - x) >> 1;}
              if (y > 0) {e2->y -= ( size - y) >> 1;}
              if (y < 0) {e2->y -= (-size - y) >> 1;}
            }
          }
        }
      }
    }
  }
  yeti->keyboard._a       = yeti->keyboard.a;
  yeti->keyboard._b       = yeti->keyboard.b;
  yeti->keyboard._select  = yeti->keyboard.select;
  yeti->keyboard._left    = yeti->keyboard.left;
  yeti->keyboard._right   = yeti->keyboard.right;
  yeti->keyboard._up      = yeti->keyboard.up;
  yeti->keyboard._down    = yeti->keyboard.down;
  yeti->keyboard._r       = yeti->keyboard.r;
  yeti->keyboard._l       = yeti->keyboard.l;

  yeti->surfaces[YETI_TEXTURE_SKY].xpan = -(yeti->camera->x>>1) + (yeti->tick<<2);
  yeti->surfaces[YETI_TEXTURE_SKY].ypan = -(yeti->camera->y>>1) + (yeti->tick<<2);
  yeti->surfaces[YETI_TEXTURE_WINDOW].xpan = -(yeti->camera->y>>1) + (yeti->tick<<2);

  yeti->surfaces[YETI_TEXTURE_LAVA ].xpan = fixsin(yeti->tick << 4) >> 2;
  yeti->surfaces[YETI_TEXTURE_LAVA ].ypan = fixcos(yeti->tick << 4) >> 2;
  yeti->surfaces[YETI_TEXTURE_WATER].xpan = fixsin(yeti->tick << 4) >> 2;
  yeti->surfaces[YETI_TEXTURE_WATER].ypan = fixcos(yeti->tick << 4) >> 2;
}

/*
** Name: yeti_ambient_lighting
** Desc: Setup a standard ambient lighting. Current lighting is removed.
** Note: This is obsolete since we have better lighting code now.
*/
void yeti_ambient_lighting(yeti_t* yeti, int lit)
{
  int x, y;

  for (y = 0; y < yeti->map_height; y++)
  {
    for (x = 0; x < yeti->map_width; x++)
    {
      yeti->cells[y][x].lit = lit;
    }
  }
}

/*
** Name: yeti_default_lighting
** Desc: Renders a default lighting setup for maps that dont have lights. This
**       is currently the only lighting system and is designed to give good
**       results for all map designs.
*/
void yeti_default_lighting(yeti_t* yeti)
{
// Lighting is not supported on Saturn, so don't need to compute it.
#ifndef __SEGA_SATURN__
  int x, y;

  yeti_ambient_lighting(yeti, i2f(0));

  for (y = 1; y < yeti->map_height - 1; y++)
  {
    for (x = 1; x < yeti->map_width - 1; x++)
    {
      cell_t* cell = yeti->cells[y] + x;
      
      if (cell->swi & CELL_SWI_LIGHT)
      {
        yeti_light(yeti, i2f(x) + FIXHALF, i2f(y) + FIXHALF);
      }
    }
  }
#endif
}

/******************************************************************************/

void* yeti_memcpy(void* d, void* s, unsigned n)
{
  while (n--) ((u8*)d)[n] = ((u8*)s)[n];
  return d;
}

void* yeti_memset(void *s, int c, unsigned n)
{
  while (n--) ((u8*)s)[n] = c;
  return s;
}

/******************************************************************************/

