/* $Id$ */

/*
 * $Log$
 * Revision 1.5  2008/07/27 16:10:37  ehofman
 *
 *
 *  - remove the SG_GLxxxx_H #defines, since OSG provides its own versions
 *  - this exposed a bizarre issue on Mac where dragging in <AGL/agl.h> in
 *    extensions.hxx was pulling in all of Carbon to the global namespace
 *    - very scary. As a result, I now need to explicitly include CoreFoundation
 *    in fg_init.cxx.
 *  - change SG_USING_STD(x) to using std::x
 *
 * Issues:
 *
 *  - the logic for X11 and Win32 in RenderTexture and extensions is tortured,
 *    please see if you agree I got all the ifdefs correct.
 *
 * Revision 1.4  2006/10/29 19:27:11  frohlich
 * Modified Files:
 * 	configure.ac simgear/environment/visual_enviro.cxx
 * 	simgear/ephemeris/ephemeris.cxx
 * 	simgear/ephemeris/ephemeris.hxx simgear/ephemeris/stardata.cxx
 * 	simgear/ephemeris/stardata.hxx simgear/math/SGMatrix.hxx
 * 	simgear/math/SGQuat.hxx simgear/math/SGVec3.hxx
 * 	simgear/math/SGVec4.hxx simgear/scene/Makefile.am
 *  	simgear/scene/material/mat.cxx simgear/scene/material/mat.hxx
 * 	simgear/scene/material/matlib.cxx
 * 	simgear/scene/material/matlib.hxx
 * 	simgear/scene/material/matmodel.cxx
 * 	simgear/scene/material/matmodel.hxx
 * 	simgear/scene/model/Makefile.am
 * 	simgear/scene/model/animation.cxx
 * 	simgear/scene/model/animation.hxx
 * 	simgear/scene/model/custtrans.hxx
 * 	simgear/scene/model/model.cxx simgear/scene/model/model.hxx
 * 	simgear/scene/model/modellib.cxx
 * 	simgear/scene/model/modellib.hxx
 * 	simgear/scene/model/personality.cxx
 * 	simgear/scene/model/personality.hxx
 * 	simgear/scene/model/placement.cxx
 * 	simgear/scene/model/placement.hxx
 * 	simgear/scene/model/placementtrans.cxx
 * 	simgear/scene/model/placementtrans.hxx
 * 	simgear/scene/model/shadanim.cxx
 * 	simgear/scene/model/shadowvolume.hxx
 * 	simgear/scene/sky/cloud.cxx simgear/scene/sky/cloud.hxx
 * 	simgear/scene/sky/cloudfield.cxx simgear/scene/sky/dome.cxx
 * 	simgear/scene/sky/dome.hxx simgear/scene/sky/moon.cxx
 * 	simgear/scene/sky/moon.hxx simgear/scene/sky/newcloud.cxx
 * 	simgear/scene/sky/oursun.cxx simgear/scene/sky/oursun.hxx
 * 	simgear/scene/sky/sky.cxx simgear/scene/sky/sky.hxx
 * 	simgear/scene/sky/sphere.cxx simgear/scene/sky/sphere.hxx
 * 	simgear/scene/sky/stars.cxx simgear/scene/sky/stars.hxx
 * 	simgear/scene/tgdb/apt_signs.cxx
 * 	simgear/scene/tgdb/apt_signs.hxx simgear/scene/tgdb/leaf.cxx
 * 	simgear/scene/tgdb/leaf.hxx simgear/scene/tgdb/obj.cxx
 * 	simgear/scene/tgdb/obj.hxx simgear/scene/tgdb/pt_lights.cxx
 * 	simgear/scene/tgdb/pt_lights.hxx
 * 	simgear/scene/tgdb/userdata.cxx
 * 	simgear/scene/tgdb/userdata.hxx simgear/scene/tgdb/vasi.hxx
 * 	simgear/screen/jpgfactory.cxx simgear/screen/tr.cxx
 * 	simgear/structure/Makefile.am simgear/threads/SGThread.hxx
 * Added Files:
 * 	simgear/scene/util/Makefile.am
 * 	simgear/scene/util/SGDebugDrawCallback.hxx
 * 	simgear/scene/util/SGNodeMasks.hxx
 * 	simgear/scene/util/SGStateAttributeVisitor.hxx
 * 	simgear/scene/util/SGTextureStateAttributeVisitor.hxx
 * 	simgear/scene/util/SGUpdateVisitor.hxx
 * Removed Files:
 * 	simgear/screen/ssgEntityArray.cxx
 * 	simgear/screen/ssgEntityArray.hxx
 * 	simgear/structure/ssgSharedPtr.hxx
 * 	Big BLOB on the way to OSG.
 *
 * Revision 1.3  2006-02-21 10:47:21  ehofman
 * Back out the previous patch.
 *
 * Revision 1.2  2004/11/18 19:10:34  curt
 * Abstract out location of gl.h, glut.h, and glu.h includes so that we can
 * make the Mac platform happy since they put these in a different place compared
 * to the rest of the world.
 *
 * Revision 1.1.1.1  2002/09/07 02:58:19  curt
 * Initial revsion of Simgear-0.3.0
 *
 * Revision 1.3  2001/07/30 20:34:21  curt
 * Various MSVC fixes.
 *
 * Revision 1.2  2001/06/27 02:48:01  curt
 * Fixed a type conversion bug that could trip up some of the pickier compilers
 * out there.
 *
 * Revision 1.1  2001/06/26 15:19:39  curt
 * Added tr.cxx / tr.h, Brian Paul's LGPL'd tiled rendering support libs for
 * rendering ultra high res "tiled" screen shots.
 *
 * Revision 1.9  1998/01/29  16:56:54  brianp
 * allow trOrtho() and trFrustum() to be called at any time, minor clean-up
 *
 * Revision 1.8  1998/01/28  19:47:39  brianp
 * minor clean-up for C++
 *
 * Revision 1.7  1997/07/21  17:34:38  brianp
 * added tile borders
 *
 * Revision 1.6  1997/07/21  15:47:35  brianp
 * renamed all "near" and "far" variables
 *
 * Revision 1.5  1997/04/26  21:23:25  brianp
 * added trRasterPos3f function
 *
 * Revision 1.4  1997/04/26  19:59:36  brianp
 * set CurrentTile to -1 before first tile and after last tile
 *
 * Revision 1.3  1997/04/22  23:51:15  brianp
 * added WIN32 header stuff, removed tabs
 *
 * Revision 1.2  1997/04/19  23:26:10  brianp
 * many API changes
 *
 * Revision 1.1  1997/04/18  21:53:05  brianp
 * Initial revision
 *
 */


/*
 * Tiled Rendering library
 * Version 1.1
 * Copyright (C) Brian Paul
 */


#include <simgear/compiler.h>

#include <assert.h>
#include <math.h>
#include <stdlib.h>
#include <stdio.h>
#ifdef WIN32
#include <windows.h>
#endif

#include <simgear/scene/util/project.hxx>

#include "tr.h"


#define DEFAULT_TILE_WIDTH  256
#define DEFAULT_TILE_HEIGHT 256
#define DEFAULT_TILE_BORDER 0


struct _TRctx {
   /* Final image parameters */
   GLint ImageWidth, ImageHeight;
   GLenum ImageFormat, ImageType;
   GLvoid *ImageBuffer;

   /* Tile parameters */
   GLint TileWidth, TileHeight;
   GLint TileWidthNB, TileHeightNB;
   GLint TileBorder;
   GLenum TileFormat, TileType;
   GLvoid *TileBuffer;

   /* Projection parameters */
   GLboolean Perspective;
   GLdouble Left;
   GLdouble Right;
   GLdouble Bottom;
   GLdouble Top;
   GLdouble Near;
   GLdouble Far;

   /* Misc */
   TRenum RowOrder;
   GLint Rows, Columns;
   GLint CurrentTile;
   GLint CurrentTileWidth, CurrentTileHeight;
   GLint CurrentRow, CurrentColumn;

   GLint ViewportSave[4];
};



/*
 * Misc setup including computing number of tiles (rows and columns).
 */
static void Setup(TRcontext *tr)
{
   if (!tr)
      return;

   tr->Columns = (tr->ImageWidth + tr->TileWidthNB - 1) / tr->TileWidthNB;
   tr->Rows = (tr->ImageHeight + tr->TileHeightNB - 1) / tr->TileHeightNB;
   tr->CurrentTile = 0;

   assert(tr->Columns >= 0);
   assert(tr->Rows >= 0);
}



TRcontext *trNew(void)
{
   TRcontext *tr = (TRcontext *) calloc(1, sizeof(TRcontext));
   if (tr) {
      tr->TileWidth = DEFAULT_TILE_WIDTH;
      tr->TileHeight = DEFAULT_TILE_HEIGHT;
      tr->TileBorder = DEFAULT_TILE_BORDER;
      tr->RowOrder = TR_BOTTOM_TO_TOP;
      tr->CurrentTile = -1;
   }
   return (TRcontext *) tr;
}


void trDelete(TRcontext *tr)
{
   if (tr)
      free(tr);
}



void trTileSize(TRcontext *tr, GLint width, GLint height, GLint border)
{
   if (!tr)
      return;

   assert(border >= 0);
   assert(width >= 1);
   assert(height >= 1);
   assert(width >= 2*border);
   assert(height >= 2*border);

   tr->TileBorder = border;
   tr->TileWidth = width;
   tr->TileHeight = height;
   tr->TileWidthNB = width - 2 * border;
   tr->TileHeightNB = height - 2 * border;
   Setup(tr);
}



void trTileBuffer(TRcontext *tr, GLenum format, GLenum type, GLvoid *image)
{
   if (!tr)
      return;

   tr->TileFormat = format;
   tr->TileType = type;
   tr->TileBuffer = image;
}



void trImageSize(TRcontext *tr, GLint width, GLint height)
{
   if (!tr)
      return;

   tr->ImageWidth = width;
   tr->ImageHeight = height;
   Setup(tr);
}


void trImageBuffer(TRcontext *tr, GLenum format, GLenum type, GLvoid *image)
{
   if (!tr)
      return;

   tr->ImageFormat = format;
   tr->ImageType = type;
   tr->ImageBuffer = image;
}


GLint trGet(TRcontext *tr, TRenum param)
{
   if (!tr)
      return 0;
   switch (param) {
      case TR_TILE_WIDTH:
         return tr->TileWidth;
      case TR_TILE_HEIGHT:
         return tr->TileHeight;
      case TR_TILE_BORDER:
         return tr->TileBorder;
      case TR_IMAGE_WIDTH:
         return tr->ImageWidth;
      case TR_IMAGE_HEIGHT:
         return tr->ImageHeight;
      case TR_ROWS:
         return tr->Rows;
      case TR_COLUMNS:
         return tr->Columns;
      case TR_CURRENT_ROW:
         if (tr->CurrentTile<0)
            return -1;
         else
            return tr->CurrentRow;
      case TR_CURRENT_COLUMN:
         if (tr->CurrentTile<0)
            return -1;
         else
            return tr->CurrentColumn;
      case TR_CURRENT_TILE_WIDTH:
         return tr->CurrentTileWidth;
      case TR_CURRENT_TILE_HEIGHT:
         return tr->CurrentTileHeight;
      case TR_ROW_ORDER:
         return (GLint) tr->RowOrder;
      default:
         return 0;
   }
}

GLdouble trGetD(TRcontext *tr, TRenum param)
{
	if (!tr)
		return 0.0;
	switch (param) {
	case TR_LEFT:
		return tr->Left;
	case TR_RIGHT:
		return tr->Right;
	case TR_BOTTOM:
		return tr->Bottom;
	case TR_TOP:
		return tr->Top;
	case TR_NEAR:
		return tr->Near;
	case TR_FAR:
		return tr->Far;
	default:
		return 0.0;
	}
}

void trRowOrder(TRcontext *tr, TRenum order)
{
   if (!tr)
      return;

   if (order==TR_TOP_TO_BOTTOM || order==TR_BOTTOM_TO_TOP)
      tr->RowOrder = order;
}


void trOrtho(TRcontext *tr,
             GLdouble left, GLdouble right,
             GLdouble bottom, GLdouble top,
             GLdouble zNear, GLdouble zFar)
{
   if (!tr)
      return;

   tr->Perspective = GL_FALSE;
   tr->Left = left;
   tr->Right = right;
   tr->Bottom = bottom;
   tr->Top = top;
   tr->Near = zNear;
   tr->Far = zFar;
}


void trFrustum(TRcontext *tr,
               GLdouble left, GLdouble right,
               GLdouble bottom, GLdouble top,
               GLdouble zNear, GLdouble zFar)
{
   if (!tr)
      return;

   tr->Perspective = GL_TRUE;
   tr->Left = left;
   tr->Right = right;
   tr->Bottom = bottom;
   tr->Top = top;
   tr->Near = zNear;
   tr->Far = zFar;
}


void trPerspective(TRcontext *tr,
                   GLdouble fovy, GLdouble aspect,
                   GLdouble zNear, GLdouble zFar )
{
   GLdouble xmin, xmax, ymin, ymax;
   ymax = zNear * tan(fovy * 3.14159265 / 360.0);
   ymin = -ymax;
   xmin = ymin * aspect;
   xmax = ymax * aspect;
   trFrustum(tr, xmin, xmax, ymin, ymax, zNear, zFar);
}


void trBeginTile(TRcontext *tr)
{
   GLint matrixMode;
   GLint tileWidth, tileHeight, border;
//   GLdouble left, right, bottom, top;

   if (!tr)
      return;

   if (tr->CurrentTile <= 0) {
      Setup(tr);
      /* Save user's viewport, will be restored after last tile rendered */
      glGetIntegerv(GL_VIEWPORT, tr->ViewportSave);
   }

   /* which tile (by row and column) we're about to render */
   if (tr->RowOrder==TR_BOTTOM_TO_TOP) {
      tr->CurrentRow = tr->CurrentTile / tr->Columns;
      tr->CurrentColumn = tr->CurrentTile % tr->Columns;
   }
   else if (tr->RowOrder==TR_TOP_TO_BOTTOM) {
      tr->CurrentRow = tr->Rows - (tr->CurrentTile / tr->Columns) - 1;
      tr->CurrentColumn = tr->CurrentTile % tr->Columns;
   }
   else {
      /* This should never happen */
      abort();
   }
   assert(tr->CurrentRow < tr->Rows);
   assert(tr->CurrentColumn < tr->Columns);

   border = tr->TileBorder;

   /* Compute actual size of this tile with border */
   if (tr->CurrentRow < tr->Rows-1)
      tileHeight = tr->TileHeight;
   else
      tileHeight = tr->ImageHeight - (tr->Rows-1) * (tr->TileHeightNB) + 2 * border;

   if (tr->CurrentColumn < tr->Columns-1)
      tileWidth = tr->TileWidth;
   else
      tileWidth = tr->ImageWidth - (tr->Columns-1) * (tr->TileWidthNB) + 2 * border;

   /* Save tile size, with border */
   tr->CurrentTileWidth = tileWidth;
   tr->CurrentTileHeight = tileHeight;

   glViewport(0, 0, tileWidth, tileHeight);  /* tile size including border */

   /* save current matrix mode */
   glGetIntegerv(GL_MATRIX_MODE, &matrixMode);
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();

   // OSGFIXME
   /* compute projection parameters */
   /*
   left = tr->Left + (tr->Right - tr->Left)
        * (tr->CurrentColumn * tr->TileWidthNB - border) / tr->ImageWidth;
   right = left + (tr->Right - tr->Left) * tileWidth / tr->ImageWidth;
   bottom = tr->Bottom + (tr->Top - tr->Bottom)
          * (tr->CurrentRow * tr->TileHeightNB - border) / tr->ImageHeight;
   top = bottom + (tr->Top - tr->Bottom) * tileHeight / tr->ImageHeight;

    ssgSetFrustum ( left, right, bottom, top, tr->Near, tr->Far );
   */

   /* restore user's matrix mode */
   glMatrixMode( (GLenum)matrixMode );
}



int trEndTile(TRcontext *tr)
{
   GLint prevRowLength, prevSkipRows, prevSkipPixels /*, prevAlignment */;

   if (!tr)
      return 0;

   assert(tr->CurrentTile>=0);

   /* be sure OpenGL rendering is finished */
   glFlush();

   /* save current glPixelStore values */
   glGetIntegerv(GL_PACK_ROW_LENGTH, &prevRowLength);
   glGetIntegerv(GL_PACK_SKIP_ROWS, &prevSkipRows);
   glGetIntegerv(GL_PACK_SKIP_PIXELS, &prevSkipPixels);
   /*glGetIntegerv(GL_PACK_ALIGNMENT, &prevAlignment);*/

   if (tr->TileBuffer) {
      GLint srcX = tr->TileBorder;
      GLint srcY = tr->TileBorder;
      GLint srcWidth = tr->TileWidthNB;
      GLint srcHeight = tr->TileHeightNB;
      glReadPixels(srcX, srcY, srcWidth, srcHeight,
                   tr->TileFormat, tr->TileType, tr->TileBuffer);
   }

   if (tr->ImageBuffer) {
      GLint srcX = tr->TileBorder;
      GLint srcY = tr->TileBorder;
      GLint srcWidth = tr->CurrentTileWidth - 2 * tr->TileBorder;
      GLint srcHeight = tr->CurrentTileHeight - 2 * tr->TileBorder;
      GLint destX = tr->TileWidthNB * tr->CurrentColumn;
      GLint destY = tr->TileHeightNB * tr->CurrentRow;

      /* setup pixel store for glReadPixels */
      glPixelStorei(GL_PACK_ROW_LENGTH, tr->ImageWidth);
      glPixelStorei(GL_PACK_SKIP_ROWS, destY);
      glPixelStorei(GL_PACK_SKIP_PIXELS, destX);
      /*glPixelStorei(GL_PACK_ALIGNMENT, 1);*/

      /* read the tile into the final image */
      glReadPixels(srcX, srcY, srcWidth, srcHeight,
                   tr->ImageFormat, tr->ImageType, tr->ImageBuffer);
   }

   /* restore previous glPixelStore values */
   glPixelStorei(GL_PACK_ROW_LENGTH, prevRowLength);
   glPixelStorei(GL_PACK_SKIP_ROWS, prevSkipRows);
   glPixelStorei(GL_PACK_SKIP_PIXELS, prevSkipPixels);
   /*glPixelStorei(GL_PACK_ALIGNMENT, prevAlignment);*/

   /* increment tile counter, return 1 if more tiles left to render */
   tr->CurrentTile++;
   if (tr->CurrentTile >= tr->Rows * tr->Columns) {
      /* restore user's viewport */
      glViewport(tr->ViewportSave[0], tr->ViewportSave[1],
                 tr->ViewportSave[2], tr->ViewportSave[3]);
      tr->CurrentTile = -1;  /* all done */
      return 0;
   }
   else
      return 1;
}

/*
 * Replacement for glRastePos3f() which avoids the problem with invalid
 * raster pos.
 */
void trRasterPos3f(TRcontext *tr, GLfloat x, GLfloat y, GLfloat z)
{
   if (tr->CurrentTile<0) {
      /* not doing tile rendering right now.  Let OpenGL do this. */
      glRasterPos3f(x, y, z);
   }
   else {
      GLdouble modelview[16], proj[16];
      GLint viewport[4];
      GLdouble winX, winY, winZ;

      /* Get modelview, projection and viewport */
      glGetDoublev(GL_MODELVIEW_MATRIX, modelview);
      glGetDoublev(GL_PROJECTION_MATRIX, proj);
      viewport[0] = 0;
      viewport[1] = 0;
      viewport[2] = tr->CurrentTileWidth;
      viewport[3] = tr->CurrentTileHeight;

      /* Project object coord to window coordinate */
      if (simgear::project(x, y, z, modelview, proj, viewport,
                           &winX, &winY, &winZ)){

         /* set raster pos to window coord (0,0) */
         glMatrixMode(GL_MODELVIEW);
         glPushMatrix();
         glLoadIdentity();
         glMatrixMode(GL_PROJECTION);
         glPushMatrix();
         glLoadIdentity();
         glOrtho(0.0, tr->CurrentTileWidth,
                 0.0, tr->CurrentTileHeight, 0.0, 1.0);
         glRasterPos3f(0.0, 0.0, -winZ);

         /* Now use empty bitmap to adjust raster position to (winX,winY) */
         {
            GLubyte bitmap[1] = {0};
            glBitmap(1, 1, 0.0, 0.0, winX, winY, bitmap);
         }

         /* restore original matrices */
         glPopMatrix(); /*proj*/
         glMatrixMode(GL_MODELVIEW);
         glPopMatrix();
      }
#ifdef DEBUG
      if (glGetError())
         printf("GL error!\n");
#endif
   }
}

