/*****************************************************************************/
/* D2K: A Doom Source Port for the 21st Century                              */
/*                                                                           */
/* Copyright (C) 2014: See COPYRIGHT file                                    */
/*                                                                           */
/* This file is part of D2K.                                                 */
/*                                                                           */
/* D2K 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.                                                            */
/*                                                                           */
/* D2K 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 D2K.  If not, see <http://www.gnu.org/licenses/>.                    */
/*                                                                           */
/*****************************************************************************/

/*
** gl_sky.cpp
**
** Draws the sky.  Loosely based on the JDoom sky and the ZDoomGL 0.66.2 sky.
**
**---------------------------------------------------------------------------
** Copyright 2003 Tim Stump
** Copyright 2005 Christoph Oelckers
** Copyright 2009 Andrey Budko
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
**
** 1. Redistributions of source code must retain the above copyright
**    notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
**    notice, this list of conditions and the following disclaimer in the
**    documentation and/or other materials provided with the distribution.
** 3. The name of the author may not be used to endorse or promote products
**    derived from this software without specific prior written permission.
** 4. Full disclosure of the entire project's source code, except for third
**    party libraries is mandatory. (NOTE: This clause is non-negotiable!)
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
**---------------------------------------------------------------------------
*/

#include "z_zone.h"

#include "doomdef.h"
#include "doomstat.h"
#include "r_defs.h"
#include "v_video.h"
#include "r_defs.h"
#include "r_data.h"
#include "r_plane.h"
#include "r_sky.h"
#include "r_main.h"
#include "r_state.h"
#include "sc_man.h"
#include "m_misc.h"
#include "e6y.h"
#include "g_game.h"

#include "gl_opengl.h"
#include "gl_intern.h"
#include "gl_struct.h"

typedef struct {
  int mode;
  int vertexcount;
  int vertexindex;
  int use_texture;
} GLSkyLoopDef;

typedef struct {
  int           id;
  int           rows, columns;
  int           loopcount;
  GLSkyLoopDef *loops;
  vbo_vertex_t *data;
} GLSkyVBO;

int gl_skymode;
int gl_drawskys;

// Sky stretching is rather pointless with the GL renderer
// now that it can handle all sky heights.
int gl_stretchsky = false;

static PalEntry_t *SkyColor;

SkyBoxParams_t SkyBox;
float y_offset_saved;

// skybox
box_skybox_t *BoxSkybox = NULL;
int BoxSkyboxCount = 0;

box_skybox_t *BoxSkybox_default;

void gld_InitSky(void) {
  memset(&SkyBox, 0, sizeof(SkyBox));
  SkyBox.index = -1;
  y_offset_saved = 0;
}

void gld_InitFrameSky(void) {
  SkyBox.type = SKY_NONE;
  SkyBox.wall.gltexture = NULL;
  SkyBox.x_scale = 0;
  SkyBox.y_scale = 0;
  SkyBox.x_offset = 0;
  SkyBox.y_offset = 0;
  SkyBox.side = NULL;
}

void gld_DrawFakeSkyStrips(void) {
  int i;

  // This draws a valid z-buffer into the stencil's contents to ensure it
  // doesn't get overwritten by the level's geometry.

  // Because some of outdated hardware has no support for
  // glColorMask(0, 0, 0, 0) or something,
  // I need to render fake strips of sky before dome with using
  // full clearing of color buffer (only in compatibility mode)

  if (!gl_compatibility) {
    glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); // no graphics
  }
  gld_EnableTexture2D(GL_TEXTURE0_ARB, false);

  for (i = gld_drawinfo.num_items[GLDIT_SWALL] - 1; i >= 0; i--) {
    GLWall *wall = gld_drawinfo.items[GLDIT_SWALL][i].item.wall;

    glBegin(GL_TRIANGLE_STRIP);
    glVertex3f(wall->glseg->x1, wall->ytop, wall->glseg->z1);
    glVertex3f(wall->glseg->x1, wall->ybottom, wall->glseg->z1);
    glVertex3f(wall->glseg->x2, wall->ytop, wall->glseg->z2);
    glVertex3f(wall->glseg->x2, wall->ybottom, wall->glseg->z2);
    glEnd();
  }

  gld_EnableTexture2D(GL_TEXTURE0_ARB, true);
  if (!gl_compatibility) {
    glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
  }
  else {
    glClear(GL_COLOR_BUFFER_BIT);
  }
}

void gld_GetScreenSkyScale(GLWall *wall, float *scale_x, float *scale_y) {
  float sx, sy;

  sx = (wall->flag == GLDWF_SKYFLIP ? -128.0f : 128.0f);

  if (!mlook_or_fov) {
    sx = sx / (float)wall->gltexture->buffer_width;
    sy = 200.0f / 160.0f;// wall->gltexture->buffer_height;
  }
  else {
    sx = sx * skyscale / (float)wall->gltexture->buffer_width;
    sy = 127.0f * skyscale / 160.0f;
  }

  *scale_x = sx;
  *scale_y = sy;
}

// Sky textures with a zero index should be forced
// See third episode of requiem.wad
void gld_AddSkyTexture(GLWall *wall, int sky1, int sky2, int skytype) {
  side_t *s = NULL;
  line_t *l = NULL;

  wall->gltexture = NULL;

  if ((sky1) & PL_SKYFLAT) {
    l = &lines[sky1 & ~PL_SKYFLAT];
  }
  else {
    if ((sky2) & PL_SKYFLAT) {
      l = &lines[sky2 & ~PL_SKYFLAT];
    }
  }

  if (l) {
    s = *l->sidenum + sides;
    SkyBox.side = s;
    wall->gltexture = gld_RegisterTexture(texturetranslation[s->toptexture],
      false,
      texturetranslation[s->toptexture] == skytexture || l->special == 271 ||
      l->special == 272);
    if (wall->gltexture) {
      if (!mlook_or_fov) {
        wall->skyyaw  = -2.0f *
          ((-(float)((viewangle + s->textureoffset) >> ANGLETOFINESHIFT) *
          360.0f / FINEANGLES) / 90.0f);
        wall->skyymid = 200.0f / 319.5f *
          (((float)s->rowoffset / (float)FRACUNIT - 28.0f) / 100.0f);
      }
      else {
        wall->skyyaw  = -2.0f *
          (((270.0f -
          (float)((viewangle + s->textureoffset) >> ANGLETOFINESHIFT) * 360.0f /
          FINEANGLES) + 90.0f) / 90.0f / skyscale);
        wall->skyymid = skyYShift +
          (((float)s->rowoffset / (float)FRACUNIT) / 100.0f);
      }
      wall->flag = (l->special == 272 ? GLDWF_SKY : GLDWF_SKYFLIP);
    }
  }
  else {
    wall->gltexture = gld_RegisterTexture(skytexture, false, true);
    if (wall->gltexture) {
      wall->skyyaw  = skyXShift;
      wall->skyymid = skyYShift;
      wall->flag = GLDWF_SKY;
    }
  }

  if (wall->gltexture) {
    SkyBox.type |= skytype;

    wall->gltexture->flags |= GLTEXTURE_SKY;

    gld_AddDrawItem(GLDIT_SWALL, wall);

    if (!SkyBox.wall.gltexture) {
      SkyBox.wall = *wall;

      switch (gl_drawskys) {
        case skytype_standard:
          gld_GetScreenSkyScale(wall, &SkyBox.x_scale, &SkyBox.y_scale);
          break;

        case skytype_screen:
          if (s) {
            SkyBox.x_offset = (float)s->textureoffset;
            SkyBox.y_offset = (float)s->rowoffset / (float)FRACUNIT;
          }
          break;
        case skytype_skydome:
          if (s) {
            SkyBox.x_offset = (float)s->textureoffset * 180.0f / (float)ANG180;
            SkyBox.y_offset = (float)s->rowoffset / (float)FRACUNIT;
          }
          break;
      }
    }
  }
}

void gld_DrawStripsSky(void) {
  int i;
  float skyymid_multiplier;
  GLTexture *gltexture = NULL;

  if (gl_drawskys == skytype_standard) {
    if (comp[comp_skymap] && gl_shared_texture_palette) {
      glDisable(GL_SHARED_TEXTURE_PALETTE_EXT);
    }

    if (comp[comp_skymap] && (invul_method & INVUL_BW)) {
      glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
    }

    glEnable(GL_TEXTURE_GEN_S);
    glEnable(GL_TEXTURE_GEN_T);
    glEnable(GL_TEXTURE_GEN_Q);
    if (comp[comp_skymap] || !(invul_method & INVUL_BW)) {
      glColor4fv(gl_whitecolor);
    }

    SetTextureMode(TM_OPAQUE);
  }

  gld_EnableDetail(false);
  glMatrixMode(GL_TEXTURE);

  skyymid_multiplier = 1.0f;
  if (tallscreen) {
    skyymid_multiplier = (float)ratio_multiplier / ratio_scale;
  }

  for (i = gld_drawinfo.num_items[GLDIT_SWALL] - 1; i >= 0; i--) {
    GLWall *wall = gld_drawinfo.items[GLDIT_SWALL][i].item.wall;

    gltexture = (gl_drawskys == skytype_none ? NULL : wall->gltexture);
    gld_BindTexture(gltexture, 0);

    if (!gltexture) {
      glColor4f(1.0f, 0.0f, 0.0f, 1.0f);
    }

    if (gltexture) {
      float sx, sy;

      glPushMatrix();

      gld_GetScreenSkyScale(wall, &sx, &sy);
      glScalef(sx, sy * skyymid_multiplier, 1.0f);
      glTranslatef(wall->skyyaw, wall->skyymid / skyymid_multiplier, 0.0f);
    }

#if 0
    {
      float r = (float)(wall->seg->sidedef - sides) / (float)(numsides - 1);
      float g = (float)wall->seg->linedef->iLineID / (float)(numlines - 1);
      float b = (float)i / (float)(gld_drawinfo.num_items[GLDIT_SWALL] - 1);
      glColor4f(r, g, b, 1.0f);
    }
#endif /* if 0 */

    glBegin(GL_TRIANGLE_STRIP);
    glVertex3f(wall->glseg->x1, wall->ytop, wall->glseg->z1);
    glVertex3f(wall->glseg->x1, wall->ybottom, wall->glseg->z1);
    glVertex3f(wall->glseg->x2, wall->ytop, wall->glseg->z2);
    glVertex3f(wall->glseg->x2, wall->ybottom, wall->glseg->z2);
    glEnd();

    if (gltexture) {
      glPopMatrix();
    }
  }

  glMatrixMode(GL_MODELVIEW);

  gld_DrawSkyCaps();

  if (gl_drawskys == skytype_standard) {
    glDisable(GL_TEXTURE_GEN_Q);
    glDisable(GL_TEXTURE_GEN_T);
    glDisable(GL_TEXTURE_GEN_S);

    if (comp[comp_skymap] && (invul_method & INVUL_BW)) {
      glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_COMBINE);
    }

    if (comp[comp_skymap] && gl_shared_texture_palette) {
      glEnable(GL_SHARED_TEXTURE_PALETTE_EXT);
    }

    SetFrameTextureMode();
  }
}

void gld_DrawSkyCaps(void) {
  if (SkyBox.type && SkyBox.wall.gltexture) {
    bool mlook = GetMouseLook();

    if (mlook) {
      gld_BindTexture(SkyBox.wall.gltexture, 0);

      glMatrixMode(GL_TEXTURE);
      glPushMatrix();

      glScalef(SkyBox.x_scale, SkyBox.y_scale, 1.0f);
      glTranslatef(SkyBox.wall.skyyaw, SkyBox.wall.skyymid, 0.0f);

      if (SkyBox.type & SKY_CEILING) {
        glBegin(GL_TRIANGLE_STRIP);
        glVertex3f(-MAXCOORD, +MAXCOORD, +MAXCOORD);
        glVertex3f(+MAXCOORD, +MAXCOORD, +MAXCOORD);
        glVertex3f(-MAXCOORD, +MAXCOORD, -MAXCOORD);
        glVertex3f(+MAXCOORD, +MAXCOORD, -MAXCOORD);
        glEnd();
      }

      if (SkyBox.type & SKY_FLOOR) {
        glBegin(GL_TRIANGLE_STRIP);
        glVertex3f(-MAXCOORD, -MAXCOORD, +MAXCOORD);
        glVertex3f(+MAXCOORD, -MAXCOORD, +MAXCOORD);
        glVertex3f(-MAXCOORD, -MAXCOORD, -MAXCOORD);
        glVertex3f(+MAXCOORD, -MAXCOORD, -MAXCOORD);
        glEnd();
      }

      glPopMatrix();
      glMatrixMode(GL_MODELVIEW);
    }
  }
}

// ===========================================================================
//
// averageColor
//  input is RGBA8 pixel format.
//	The resulting RGB color can be scaled uniformly so that the highest
//	component becomes one.
//
// ===========================================================================
#define APART(c) (((c) >> 24) & 0xff)
#define RPART(c) (((c) >> 16) & 0xff)
#define GPART(c) (((c) >> 8) & 0xff)
#define BPART(c) ((c) & 0xff)

void averageColor(PalEntry_t *PalEntry,
  const unsigned int         *data,
  int                         size,
  fixed_t                     maxout_factor) {
  int i;
  int maxv;
  unsigned int r, g, b;

  // First clear them.
  r = g = b = 0;
  if (size == 0) {
    PalEntry->r = 255;
    PalEntry->g = 255;
    PalEntry->b = 255;
    return;
  }
  for (i = 0; i < size; i++) {
    r += BPART(data[i]);
    g += GPART(data[i]);
    b += RPART(data[i]);
  }

  r = r / size;
  g = g / size;
  b = b / size;

  maxv = MAX(MAX(r, g), b);

  if (maxv && maxout_factor) {
    maxout_factor = FixedMul(maxout_factor, 255);
    r = r * maxout_factor / maxv;
    g = g * maxout_factor / maxv;
    b = b * maxout_factor / maxv;
  }

  PalEntry->r = r;
  PalEntry->g = g;
  PalEntry->b = b;
}

// It is an alternative way of drawing the sky (gl_drawskys == skytype_screen)
// This method make sense only for old hardware which have no support for
// GL_TEXTURE_GEN_*
// Voodoo as example
void gld_DrawScreenSkybox(void) {
  if (SkyBox.wall.gltexture) {
#define WRAPANGLE (ANGLE_MAX / 4)

    float fU1, fU2, fV1, fV2;
    GLWall *wall = &SkyBox.wall;
    angle_t angle;
    int i, k;
    float w;

    if (!gl_compatibility) {
      glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE); // no graphics
    }
    gld_EnableTexture2D(GL_TEXTURE0_ARB, false);

    for (i = gld_drawinfo.num_items[GLDIT_SWALL] - 1; i >= 0; i--) {
      GLWall *wall = gld_drawinfo.items[GLDIT_SWALL][i].item.wall;

      glBegin(GL_TRIANGLE_STRIP);
      glVertex3f(wall->glseg->x1, wall->ytop, wall->glseg->z1);
      glVertex3f(wall->glseg->x1, wall->ybottom, wall->glseg->z1);
      glVertex3f(wall->glseg->x2, wall->ytop, wall->glseg->z2);
      glVertex3f(wall->glseg->x2, wall->ybottom, wall->glseg->z2);
      glEnd();
    }

    gld_EnableTexture2D(GL_TEXTURE0_ARB, true);
    if (!gl_compatibility) {
      glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
    }
    else {
      glClear(GL_COLOR_BUFFER_BIT);
    }

    if (!mlook_or_fov) {
      fV1 = SkyBox.y_offset / 127.0f;
      fV2 = fV1 + 320.0f / 200.0f;
    }
    else {
      float f = viewPitch * 2 + 40 / skyscale;
      f = BETWEEN(0, 127, f);
      fV1 = (f + SkyBox.y_offset) / 127.0f * skyscale;
      fV2 = fV1 + 1.0f;
    }

    k = MAX(wall->gltexture->buffer_width, 256) / 256;
    angle = ((viewangle - ANG45) / k) % WRAPANGLE;

    if (wall->flag == GLDWF_SKYFLIP) {
      fU1 = -((float)angle + SkyBox.x_offset) / (WRAPANGLE - 1);
      fU2 = fU1 + 1.0f / k;
    }
    else {
      fU2 = ((float)angle + SkyBox.x_offset) / (WRAPANGLE - 1);
      fU1 = fU2 + 1.0f / k;
    }

    glDisable(GL_DEPTH_TEST);
    glDisable(GL_ALPHA_TEST);

    glPushMatrix();
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
    gld_BindTexture(wall->gltexture, 0);
    w = 160.0f * SCREENWIDTH / WIDE_SCREENWIDTH;
    glBegin(GL_TRIANGLE_STRIP);
    glTexCoord2f(fU1, fV1);
    glVertex3f(-w, +100.5f, -screen_skybox_zplane);
    glTexCoord2f(fU1, fV2);
    glVertex3f(-w, -100.5f, -screen_skybox_zplane);
    glTexCoord2f(fU2, fV1);
    glVertex3f(+w, +100.5f, -screen_skybox_zplane);
    glTexCoord2f(fU2, fV2);
    glVertex3f(+w, -100.5f, -screen_skybox_zplane);
    glEnd();

    glPopMatrix();

    glEnable(GL_ALPHA_TEST);
    glEnable(GL_DEPTH_TEST);
  }
}

// The texture offset to be applied to the texture coordinates in SkyVertex().
static int rows, columns;
static int yflip;
static int texw;
static float yMult, yAdd;
static bool foglayer;
static float delta = 0.0f;

int gl_sky_detail = 16;

// -----------------------------------------------------------------------------
//
//
//
// -----------------------------------------------------------------------------

void gld_GetSkyCapColors(void) {
  int color, width, height;
  int frame_fixedcolormap_saved;
  unsigned char *buffer = NULL;
  const unsigned char *playpal = V_GetPlaypal();
  const lighttable_t *colormap;
  const lighttable_t *fixedcolormap_saved;
  PalEntry_t *ceiling_rgb = &SkyBox.CeilingSkyColor[0];
  PalEntry_t *floor_rgb = &SkyBox.FloorSkyColor[0];

  // saving current colormap
  fixedcolormap_saved = fixedcolormap;
  frame_fixedcolormap_saved = frame_fixedcolormap;

  fixedcolormap = fullcolormap;
  frame_fixedcolormap = 0;

  gld_BindTexture(SkyBox.wall.gltexture, 0);

  glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &width);
  glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &height);

  buffer = malloc(width * height * 4);
  glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_UNSIGNED_BYTE, buffer);

  averageColor(ceiling_rgb, (unsigned int *)buffer, width * MIN(30, height), 0);

  if (height > 30) {
    averageColor(floor_rgb,
      ((unsigned int *)buffer) + (height - 30) * width, width * 30, 0);
  }
  else {
    *floor_rgb = *ceiling_rgb;
  }

  colormap = fullcolormap + INVERSECOLORMAP * 256 * sizeof(lighttable_t);

  color = V_BestColor(playpal, ceiling_rgb->r, ceiling_rgb->g, ceiling_rgb->b);
  SkyBox.CeilingSkyColor[1].r = playpal[colormap[color] * 3 + 0];
  SkyBox.CeilingSkyColor[1].g = playpal[colormap[color] * 3 + 1];
  SkyBox.CeilingSkyColor[1].b = playpal[colormap[color] * 3 + 2];

  color = V_BestColor(playpal, floor_rgb->r, floor_rgb->g, floor_rgb->b);
  SkyBox.FloorSkyColor[1].r = playpal[colormap[color] * 3 + 0];
  SkyBox.FloorSkyColor[1].g = playpal[colormap[color] * 3 + 1];
  SkyBox.FloorSkyColor[1].b = playpal[colormap[color] * 3 + 2];

  // restorin current colormap
  fixedcolormap = fixedcolormap_saved;
  frame_fixedcolormap = frame_fixedcolormap_saved;

  free(buffer);
}

// -----------------------------------------------------------------------------
//
//
//
// -----------------------------------------------------------------------------

static void SkyVertex(vbo_vertex_t *vbo, int r, int c) {
  static fixed_t scale = 10000 << FRACBITS;
  static angle_t maxSideAngle = ANG180 / 3;

  angle_t topAngle = (angle_t)(c / (float)columns * ANGLE_MAX);
  angle_t sideAngle = maxSideAngle * (rows - r) / rows;
  fixed_t height = finesine[sideAngle >> ANGLETOFINESHIFT];
  fixed_t realRadius =
    FixedMul(scale, finecosine[sideAngle >> ANGLETOFINESHIFT]);
  fixed_t x = FixedMul(realRadius, finecosine[topAngle >> ANGLETOFINESHIFT]);
  fixed_t y = (!yflip) ? FixedMul(scale, height) : FixedMul(scale, height) * -1;
  fixed_t z = FixedMul(realRadius, finesine[topAngle >> ANGLETOFINESHIFT]);
  float timesRepeat;

  timesRepeat = (short)(4 * (256.0f / texw));
  if (timesRepeat == 0.0f) {
    timesRepeat = 1.0f;
  }

  if (!foglayer) {
    vbo->r = 255;
    vbo->g = 255;
    vbo->b = 255;
    vbo->a = (r == 0 ? 0 : 255);

    // And the texture coordinates.
    if (!yflip) { // Flipped Y is for the lower hemisphere.
      vbo->u = (-timesRepeat * c / (float)columns);
      vbo->v = (r / (float)rows) * 1.f * yMult + yAdd;
    }
    else {
      vbo->u = (-timesRepeat * c / (float)columns);
      vbo->v = ((rows - r) / (float)rows) * 1.f * yMult + yAdd;
    }

    if (SkyBox.wall.flag == GLDWF_SKYFLIP) {
      vbo->u = -vbo->u;
    }
  }

  if (r != 4) {
    y += FRACUNIT * 300;
  }

  // And finally the vertex.
  vbo->x = -(float)x / (float)MAP_SCALE;   // Doom mirrors the sky vertically!
  vbo->y = (float)y / (float)MAP_SCALE + delta;
  vbo->z = (float)z / (float)MAP_SCALE;
}

GLSkyVBO sky_vbo[2];

static void gld_BuildSky(int row_count,
  int                        col_count,
  SkyBoxParams_t            *sky,
  int                        cm) {
  int texh, c, r;
  vbo_vertex_t *vertex_p;
  int vertex_count = 2 * row_count * (col_count * 2 + 2) + col_count * 2;
  int vbo_idx = (cm == INVERSECOLORMAP ? 1 : 0);
  GLSkyVBO *vbo = &sky_vbo[vbo_idx];

  if ((vbo->columns != col_count) || (vbo->rows != row_count)) {
    free(vbo->loops);
    free(vbo->data);
    memset(vbo, 0, sizeof(vbo[0]));
  }

  if (!vbo->data) {
    memset(vbo, 0, sizeof(vbo[0]));
    vbo->loops = malloc((row_count * 2 + 2) * sizeof(vbo->loops[0]));

    // create vertex array
    vbo->data = malloc(vertex_count * sizeof(vbo->data[0]));
  }

  vbo->columns = col_count;
  vbo->rows = row_count;

  texh = sky->wall.gltexture->buffer_height;
  if (texh > 190 && gl_stretchsky) {
    texh = 190;
  }
  texw = sky->wall.gltexture->buffer_width;

  vertex_p = &vbo->data[0];
  vbo->loopcount = 0;
  for (yflip = 0; yflip < 2; yflip++) {
    vbo->loops[vbo->loopcount].mode = GL_TRIANGLE_FAN;
    vbo->loops[vbo->loopcount].vertexindex = vertex_p - &vbo->data[0];
    vbo->loops[vbo->loopcount].vertexcount = col_count;
    vbo->loops[vbo->loopcount].use_texture = false;
    vbo->loopcount++;

    yAdd = sky->y_offset / texh;
    yMult = (texh <= 180 ? 1.0f : 180.0f / texh);
    if (yflip == 0) {
      SkyColor = &sky->CeilingSkyColor[vbo_idx];
    }
    else {
      SkyColor = &sky->FloorSkyColor[vbo_idx];
      if (texh <= 180) {
        yMult = 1.0f;
      }
      else {
        yAdd += 180.0f / texh;
      }
    }

    delta = 0.0f;
    foglayer = true;
    for (c = 0; c < col_count; c++) {
      SkyVertex(vertex_p, 1, c);
      vertex_p->r = SkyColor->r;
      vertex_p->g = SkyColor->g;
      vertex_p->b = SkyColor->b;
      vertex_p->a = 255;
      vertex_p++;
    }
    foglayer = false;

    delta = (yflip ? 5.0f : -5.0f) / MAP_COEFF;

    for (r = 0; r < row_count; r++) {
      vbo->loops[vbo->loopcount].mode = GL_TRIANGLE_STRIP;
      vbo->loops[vbo->loopcount].vertexindex = vertex_p - &vbo->data[0];
      vbo->loops[vbo->loopcount].vertexcount = 2 * col_count + 2;
      vbo->loops[vbo->loopcount].use_texture = true;
      vbo->loopcount++;

      for (c = 0; c <= col_count; c++) {
        SkyVertex(vertex_p++, r + (yflip ? 1 : 0), (c ? c : 0));
        SkyVertex(vertex_p++, r + (yflip ? 0 : 1), (c ? c : 0));
      }
    }
  }
}

// -----------------------------------------------------------------------------
//
//
//
// -----------------------------------------------------------------------------

static void RenderDome(SkyBoxParams_t *sky) {
  int i, j;
  int vbosize;
  GLSkyVBO *vbo;

  if (!sky || !sky->wall.gltexture) {
    return;
  }

  if (invul_method == INVUL_CM && frame_fixedcolormap == INVERSECOLORMAP) {
    vbo = &sky_vbo[1];
  }
  else {
    vbo = &sky_vbo[0];
  }

#if defined(USE_VERTEX_ARRAYS) || defined(USE_VBO)

  // be sure the second ARB is not enabled
  gld_EnableDetail(false);
#endif /* if defined(USE_VERTEX_ARRAYS) || defined(USE_VBO) */

  glRotatef(-180.0f + sky->x_offset, 0.f, 1.f, 0.f);

  rows = 4;
  columns = 4 * gl_sky_detail;

  vbosize = 2 * rows * (columns * 2 + 2) + columns * 2;

  if (sky->y_offset != y_offset_saved ||
    sky->wall.gltexture->index != sky->index) {
    y_offset_saved = sky->y_offset;

    if (sky->wall.gltexture->index != sky->index) {
      sky->index = sky->wall.gltexture->index;
      gld_GetSkyCapColors();
    }

    gld_BuildSky(rows, columns, sky, 0);
    gld_BuildSky(rows, columns, sky, INVERSECOLORMAP);

#ifdef USE_VBO
    if (gl_ext_arb_vertex_buffer_object) {
      if (vbo->id) {
        // delete VBO when already exists
        GLEXT_glDeleteBuffersARB(1, (const GLuint *)&vbo->id);
      }

      // generate a new VBO and get the associated ID
      GLEXT_glGenBuffersARB(1, (GLuint *)&vbo->id);

      // bind VBO in order to use
      GLEXT_glBindBufferARB(GL_ARRAY_BUFFER, vbo->id);

      // upload data to VBO
      GLEXT_glBufferDataARB(GL_ARRAY_BUFFER,
        vbosize * sizeof(vbo->data[0]),
        vbo->data, GL_STATIC_DRAW_ARB);
    }
#endif /* ifdef USE_VBO */
  }

  gld_BindTexture(SkyBox.wall.gltexture, 0);

#if defined(USE_VERTEX_ARRAYS) || defined(USE_VBO)
  if (gl_ext_arb_vertex_buffer_object) {
    // bind VBO in order to use
    GLEXT_glBindBufferARB(GL_ARRAY_BUFFER, vbo->id);
  }

  // activate and specify pointers to arrays
  glVertexPointer(3, GL_FLOAT, sizeof(vbo->data[0]), sky_vbo_x);
  glTexCoordPointer(2, GL_FLOAT, sizeof(vbo->data[0]), sky_vbo_u);
  glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(vbo->data[0]), sky_vbo_r);

  // activate vertex array, texture coord array and color arrays
  glEnableClientState(GL_VERTEX_ARRAY);
  glEnableClientState(GL_TEXTURE_COORD_ARRAY);
  glEnableClientState(GL_COLOR_ARRAY);
#endif /* if defined(USE_VERTEX_ARRAYS) || defined(USE_VBO) */

  if (!gl_stretchsky) {
    int texh = sky->wall.gltexture->buffer_height;

    if (texh <= 180) {
      glScalef(1.0f, (float)texh / 230.0f, 1.0f);
    }
    else {
      if (texh > 190) {
        glScalef(1.0f, 230.0f / 240.0f, 1.0f);
      }
    }
  }

  for (j = (HaveMouseLook() || !gl_stretchsky ? 0 : 1); j < 2; j++) {
    gld_EnableTexture2D(GL_TEXTURE0_ARB, j != 0);

    for (i = 0; i < vbo->loopcount; i++) {
      GLSkyLoopDef *loop = &vbo->loops[i];

      if (j == 0 ? loop->use_texture : !loop->use_texture) {
        continue;
      }

#if defined(USE_VERTEX_ARRAYS) || defined(USE_VBO)
      glDrawArrays(loop->mode, loop->vertexindex, loop->vertexcount);
#else  /* if defined(USE_VERTEX_ARRAYS) || defined(USE_VBO) */
      {
        int k;
        glBegin(loop->mode);
        for (k = loop->vertexindex;
          k < (loop->vertexindex + loop->vertexcount);
          k++) {
          vbo_vertex_t *v = &vbo->data[k];
          if (loop->use_texture) {
            glTexCoord2fv((GLfloat *)&v->u);
          }
          glColor4ubv((GLubyte *)&v->r);
          glVertex3fv((GLfloat *)&v->x);
        }
        glEnd();
      }
#endif /* if defined(USE_VERTEX_ARRAYS) || defined(USE_VBO) */
    }
  }

  glScalef(1.0f, 1.0f, 1.0f);

  // current color is undefined after glDrawArrays
  glColor4f(1.0f, 1.0f, 1.0f, 1.0f);

#if defined(USE_VERTEX_ARRAYS) || defined(USE_VBO)
  if (gl_ext_arb_vertex_buffer_object) {
    // bind with 0, so, switch back to normal pointer operation
    GLEXT_glBindBufferARB(GL_ARRAY_BUFFER, 0);
  }

  // deactivate color array
  glDisableClientState(GL_COLOR_ARRAY);
#endif /* if defined(USE_VERTEX_ARRAYS) || defined(USE_VBO) */
}

void gld_DrawDomeSkyBox(void) {
  if (SkyBox.wall.gltexture) {
    GLint shading_mode = GL_FLAT;

    gld_DrawFakeSkyStrips();

    glGetIntegerv(GL_SHADE_MODEL, &shading_mode);
    glShadeModel(GL_SMOOTH);

    glDepthMask(false);

    glDisable(GL_DEPTH_TEST);
    glDisable(GL_ALPHA_TEST);
    SetTextureMode(TM_OPAQUE);

    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    glPushMatrix();
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    glRotatef(roll, 0.0f, 0.0f, 1.0f);
    glRotatef(pitch, 1.0f, 0.0f, 0.0f);
    glRotatef(yaw, 0.0f, 1.0f, 0.0f);
    glScalef(-2.0f, 2.0f, 2.0f);
    glTranslatef(0.f, -1250.0f / MAP_COEFF, 0.f);

    RenderDome(&SkyBox);

    glPopMatrix();

    glEnable(GL_ALPHA_TEST);
    glEnable(GL_DEPTH_TEST);
    glDepthMask(true);

    SetFrameTextureMode();

    glShadeModel(shading_mode);
  }
}

int R_BoxSkyboxNumForName(const char *name) {
  int i;

  for (i = 0; i < BoxSkyboxCount; i++) {
    if (!strcasecmp(BoxSkybox[i].name, name)) {
      return i;
    }
  }

  return -1;
}

void R_SetBoxSkybox(int texture) {
  int i;

  BoxSkybox_default = NULL;

  for (i = 0; i < BoxSkyboxCount; i++) {
    if (R_CheckTextureNumForName(BoxSkybox[i].name) == texture) {
      BoxSkybox_default = &BoxSkybox[i];
      return;
    }
  }
}

box_skybox_t* R_GetBoxSkybox(int index) {
  if (index >= 0 && index < BoxSkyboxCount) {
    return &BoxSkybox[index];
  }
  else {
    return NULL;
  }
}

void gld_ParseSkybox(void) {
  if (SC_GetString()) {
    box_skybox_t sb;
    memset(&sb, 0, sizeof(sb));

    strncpy(sb.name, sc_String, 8);
    sb.name[8] = 0;
    M_Strupr(sb.name);

    while (SC_Check()) {
      SC_GetString();
      if (SC_Compare("fliptop")) {
        sb.fliptop = true;
      }
    }

    if (SC_GetString() && SC_Compare("{")) {
      int facecount = 0;

      while (SC_GetString() && !SC_Compare("}")) {
        if (facecount < 6) {
          strcpy(sb.faces[facecount], sc_String);
        }
        facecount++;
      }

      if (SC_Compare("}") && (facecount == 3 || facecount == 6)) {
        int i;
        int ok = true;

        for (i = 0; i < facecount; i++) {
          if (R_CheckTextureNumForName(sb.faces[i]) == -1) {
            ok = false;
            break;
          }
        }

        if (ok) {
          BoxSkyboxCount++;
          BoxSkybox = realloc(BoxSkybox, BoxSkyboxCount * sizeof(BoxSkybox[0]));
          memcpy(&BoxSkybox[BoxSkyboxCount - 1], &sb, sizeof(sb));
        }
      }
    }
  }

  R_SetBoxSkybox(skytexture);
}

int gld_BindFace(box_skybox_t *sb, int index) {
  int lump;
  GLTexture *gltexture;
  char *name = sb->faces[index];

#if 0
  lump = W_CheckNumForName(name);
  if (lump != -1) {
    gltexture = gld_RegisterPatch(lump, CR_DEFAULT);
    gltexture->wrap_mode = GLEXT_CLAMP_TO_EDGE;
    gld_BindPatch(gltexture, CR_DEFAULT);
    return true;
  }

  // lump = R_FlatNumForName(name);
  lump = (W_CheckNumForName)(name, ns_flats);
  if (lump != -1) {
    lump -= firstflat;
    gltexture = gld_RegisterFlat(lump, true);
    gltexture->wrap_mode = GLEXT_CLAMP_TO_EDGE;
    gld_BindFlat(gltexture);
    return true;
  }
#endif /* if 0 */

  lump = R_CheckTextureNumForName(name);
  if (lump != -1) {
    gltexture = gld_RegisterTexture(lump, false, false);
    gld_BindTexture(gltexture, GLTEXTURE_CLAMPXY);
    return true;
  }

  return false;
}

int gld_DrawBoxSkyBox(void) {
  int faces;
  box_skybox_t *sb;

  if (BoxSkyboxCount == 0) {
    return false;
  }

  if (SkyBox.side) {
    sb = R_GetBoxSkybox(SkyBox.side->skybox_index);
  }
  else {
    sb = BoxSkybox_default;
  }

  if (!sb) {
    return false;
  }

  gld_DrawFakeSkyStrips();

  glDepthMask(false);

  glDisable(GL_DEPTH_TEST);
  glDisable(GL_ALPHA_TEST);

  SetTextureMode(TM_OPAQUE);

  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

  glMatrixMode(GL_MODELVIEW);
  glPushMatrix();

  glLoadIdentity();

  glRotatef(roll, 0.0f, 0.0f, 1.0f);
  glRotatef(pitch, 1.0f, 0.0f, 0.0f);
  glRotatef(yaw, 0.0f, 1.0f, 0.0f);
  glScalef(-2.0f, 2.0f, 2.0f);

  if (SkyBox.side) {
    float xoffset = (float)SkyBox.side->textureoffset * 180.0f / (float)ANG180;
    glRotatef(-180.0f + xoffset, 0.0f, 1.0f, 0.0f);
  }

  if (sb->faces[5][0]) {
    faces = 4;

    // north
    gld_BindFace(sb, 0);

    glBegin(GL_TRIANGLE_FAN);
    glTexCoord2f(0.0f, 0.0f);
    glVertex3f(+MAXCOORD, MAXCOORD, -MAXCOORD);
    glTexCoord2f(1.0f, 0.0f);
    glVertex3f(-MAXCOORD, +MAXCOORD, -MAXCOORD);
    glTexCoord2f(1.0f, 1.0f);
    glVertex3f(-MAXCOORD, -MAXCOORD, -MAXCOORD);
    glTexCoord2f(0.0f, 1.0f);
    glVertex3f(+MAXCOORD, -MAXCOORD, -MAXCOORD);
    glEnd();

    // east
    gld_BindFace(sb, 1);

    glBegin(GL_TRIANGLE_FAN);
    glTexCoord2f(0.0f, 0.0f);
    glVertex3f(-MAXCOORD, +MAXCOORD, -MAXCOORD);
    glTexCoord2f(1.0f, 0.0f);
    glVertex3f(-MAXCOORD, +MAXCOORD, +MAXCOORD);
    glTexCoord2f(1.0f, 1.0f);
    glVertex3f(-MAXCOORD, -MAXCOORD, +MAXCOORD);
    glTexCoord2f(0.0f, 1.0f);
    glVertex3f(-MAXCOORD, -MAXCOORD, -MAXCOORD);
    glEnd();

    // south
    gld_BindFace(sb, 2);

    glBegin(GL_TRIANGLE_FAN);
    glTexCoord2f(0.0f, 0.0f);
    glVertex3f(-MAXCOORD, +MAXCOORD, +MAXCOORD);
    glTexCoord2f(1.0f, 0.0f);
    glVertex3f(+MAXCOORD, +MAXCOORD, +MAXCOORD);
    glTexCoord2f(1.0f, 1.0f);
    glVertex3f(+MAXCOORD, -MAXCOORD, +MAXCOORD);
    glTexCoord2f(0.0f, 1.0f);
    glVertex3f(-MAXCOORD, -MAXCOORD, +MAXCOORD);
    glEnd();

    // west
    gld_BindFace(sb, 3);

    glBegin(GL_TRIANGLE_FAN);
    glTexCoord2f(0.0f, 0.0f);
    glVertex3f(+MAXCOORD, +MAXCOORD, +MAXCOORD);
    glTexCoord2f(1.0f, 0.0f);
    glVertex3f(+MAXCOORD, +MAXCOORD, -MAXCOORD);
    glTexCoord2f(1.0f, 1.0f);
    glVertex3f(+MAXCOORD, -MAXCOORD, -MAXCOORD);
    glTexCoord2f(0.0f, 1.0f);
    glVertex3f(+MAXCOORD, -MAXCOORD, +MAXCOORD);
    glEnd();
  }
  else {
    faces = 1;

    // all 4 sides
    gld_BindFace(sb, 0);

    glBegin(GL_TRIANGLE_FAN);
    glTexCoord2f(0.0f, 0.0f);
    glVertex3f(+MAXCOORD, +MAXCOORD, -MAXCOORD);
    glTexCoord2f(.25f, 0);
    glVertex3f(-MAXCOORD, +MAXCOORD, -MAXCOORD);
    glTexCoord2f(.25f, 1);
    glVertex3f(-MAXCOORD, -MAXCOORD, -MAXCOORD);
    glTexCoord2f(0.0f, 1.0f);
    glVertex3f(+MAXCOORD, -MAXCOORD, -MAXCOORD);
    glEnd();

    // east
    glBegin(GL_TRIANGLE_FAN);
    glTexCoord2f(.25f, 0);
    glVertex3f(-MAXCOORD, +MAXCOORD, -MAXCOORD);
    glTexCoord2f(.5f, 0);
    glVertex3f(-MAXCOORD, +MAXCOORD, +MAXCOORD);
    glTexCoord2f(.5f, 1);
    glVertex3f(-MAXCOORD, -MAXCOORD, +MAXCOORD);
    glTexCoord2f(.25f, 1);
    glVertex3f(-MAXCOORD, -MAXCOORD, -MAXCOORD);
    glEnd();

    // south
    glBegin(GL_TRIANGLE_FAN);
    glTexCoord2f(.5f, 0);
    glVertex3f(-MAXCOORD, +MAXCOORD, +MAXCOORD);
    glTexCoord2f(.75f, 0);
    glVertex3f(+MAXCOORD, +MAXCOORD, +MAXCOORD);
    glTexCoord2f(.75f, 1);
    glVertex3f(+MAXCOORD, -MAXCOORD, +MAXCOORD);
    glTexCoord2f(.5f, 1);
    glVertex3f(-MAXCOORD, -MAXCOORD, +MAXCOORD);
    glEnd();

    // west
    glBegin(GL_TRIANGLE_FAN);
    glTexCoord2f(.75f, 0);
    glVertex3f(+MAXCOORD, +MAXCOORD, +MAXCOORD);
    glTexCoord2f(1.0f, 0.0f);
    glVertex3f(+MAXCOORD, +MAXCOORD, -MAXCOORD);
    glTexCoord2f(1.0f, 1.0f);
    glVertex3f(+MAXCOORD, -MAXCOORD, -MAXCOORD);
    glTexCoord2f(.75f, 1);
    glVertex3f(+MAXCOORD, -MAXCOORD, +MAXCOORD);
    glEnd();
  }

  // top
  gld_BindFace(sb, faces);
  glBegin(GL_TRIANGLE_FAN);
  if (!sb->fliptop) {
    glTexCoord2f(0.0f, 0.0f);
    glVertex3f(+MAXCOORD, +MAXCOORD, -MAXCOORD);
    glTexCoord2f(1.0f, 0.0f);
    glVertex3f(-MAXCOORD, +MAXCOORD, -MAXCOORD);
    glTexCoord2f(1.0f, 1.0f);
    glVertex3f(-MAXCOORD, +MAXCOORD, +MAXCOORD);
    glTexCoord2f(0.0f, 1.0f);
    glVertex3f(+MAXCOORD, +MAXCOORD, +MAXCOORD);
  }
  else {
    glTexCoord2f(0.0f, 0.0f);
    glVertex3f(+MAXCOORD, +MAXCOORD, +MAXCOORD);
    glTexCoord2f(1.0f, 0.0f);
    glVertex3f(-MAXCOORD, +MAXCOORD, +MAXCOORD);
    glTexCoord2f(1.0f, 1.0f);
    glVertex3f(-MAXCOORD, +MAXCOORD, -MAXCOORD);
    glTexCoord2f(0.0f, 1.0f);
    glVertex3f(+MAXCOORD, +MAXCOORD, -MAXCOORD);
  }
  glEnd();

  // bottom
  gld_BindFace(sb, faces + 1);

  glBegin(GL_TRIANGLE_FAN);
  glTexCoord2f(0.0f, 0.0f);
  glVertex3f(+MAXCOORD, -MAXCOORD, -MAXCOORD);
  glTexCoord2f(1.0f, 0.0f);
  glVertex3f(-MAXCOORD, -MAXCOORD, -MAXCOORD);
  glTexCoord2f(1.0f, 1.0f);
  glVertex3f(-MAXCOORD, -MAXCOORD, +MAXCOORD);
  glTexCoord2f(0.0f, 1.0f);
  glVertex3f(+MAXCOORD, -MAXCOORD, +MAXCOORD);
  glEnd();

  glPopMatrix();

  glEnable(GL_ALPHA_TEST);
  glEnable(GL_DEPTH_TEST);
  glDepthMask(true);

  SetFrameTextureMode();

  return true;
}

/* vi: set et ts=2 sw=2: */
