/*
 GNU Maverik - a system for managing display and interaction in 
               Virtual Environment applications.
 Copyright (C) 1999 Advanced Interfaces Group

 This program 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


 The authors can be contacted via:
 www   - http://aig.cs.man.ac.uk
 email - maverik@aig.cs.man.ac.uk
 mail  - Advanced Interfaces Group, Room 2.90, Computer Science Building, 
         University of Manchester, Manchester, M13 9PL, UK
*/


#include "mavlib_callbacks.h"
#include <stdlib.h>
#include <stdio.h>



/* Wrapper routines to set and execute draw callback */

MAV_callback *mav_callback_draw;

void mav_callbackDrawSet(MAV_window *w, MAV_class *c, MAV_callbackDrawFn fn)
{
  mav_callbackSet(mav_callback_draw, w, c, (MAV_callbackFn) fn);
}

int mav_callbackDrawExec(MAV_window *w, MAV_object *o, MAV_drawInfo *di)
{
  int rv;

  /* check if its textured */
  if (mav_opt_delayTexture && mav_objectIsTextured(mav_win_current, o))
  {
    mav_texturedObjectsManage(o, di);
    rv= 1;
  } /* check if its transparent */
  else if (mav_opt_trans && mav_objectIsTransparent(mav_win_current, o))
  {
    mav_transparentObjectsManage(o, di);
    rv= 1;
  }
  else
  {
    rv= mav_callbackExec(mav_callback_draw, w, o, di, NULL);
  }

  return rv;
}



/* Support routines for draw */

/* Routine to translate a drawInfo structure defined in one coordinate frame into another */

MAV_drawInfo mav_drawInfoTransFrame(MAV_drawInfo di, MAV_matrix mat)
{
  MAV_drawInfo rv;
  MAV_vector pt;
  MAV_matrix imat;
  int i;

/* Account for transformation */

  imat= mav_matrixInverse(mat);

  rv.vp.eye= mav_vectorMult(di.vp.eye, imat);
  rv.vp.view= mav_vectorNormalize(mav_vectorMult3x3(di.vp.view, imat));
  rv.vp.up= mav_vectorNormalize(mav_vectorMult3x3(di.vp.up, imat));
  rv.vp.right= mav_vectorNormalize(mav_vectorMult3x3(di.vp.right, imat));

  rv.cp.num= di.cp.num;
  for (i=0; i<rv.cp.num; i++) {
    rv.cp.planes[i].norm= mav_vectorNormalize(mav_vectorMult3x3(di.cp.planes[i].norm, imat));
    pt= mav_vectorScalar(di.cp.planes[i].norm, di.cp.planes[i].d);
    pt= mav_vectorMult(pt, imat);
    rv.cp.planes[i].d= mav_vectorDotProduct(pt, rv.cp.planes[i].norm);
  }

  return rv;
}



/* Routine to work out if an object is transparent */

int mav_objectIsTransparent(MAV_window *w, MAV_object *o)
{
  MAV_surfaceParams **sp;

  return (mav_callbackGetSurfaceParamsExec(w, o, &sp) && mav_surfaceParamsIsTransparent(w, *sp));
}



/* Routine to deal with transparent object (store and render later) */

void mav_transparentObjectsManage(MAV_object *o, MAV_drawInfo *di)
{
  MAV_transObjData *to= mav_malloc(sizeof(MAV_transObjData));
  MAV_matrix *m;
  MAV_vector wpos;

  /* store transparent object */
  to->obj= o;
  to->fn= (MAV_callbackDrawFn) mav_callbackQuery(mav_callback_draw, mav_win_current, o);
  if (di) 
  {
    to->di= *di;
    to->dip= &to->di;
  }
  else 
  {
    to->dip= NULL;
  }

  /* store the pre-multiplying matrix */
  if (mav_opt_trackMatrix) 
  {
    to->mat= mav_win_current->viewMat;
  }
  else
  {
    to->mat= mav_gfxMatrixGet();
  }

  /* store the distance squared from eye */
  if (mav_callbackGetMatrixExec(mav_win_current, o, &m)) 
  {
    wpos= mav_vectorMult(mav_matrixXYZGet(*m), to->mat);
    to->dist2= mav_vectorDotProduct(wpos, wpos);

    /* add to transparent object list */
    mav_listItemAdd(mav_transObjList, (void *) to);
  }
  else
  {
    mav_free(to);
    fprintf(stderr, "Warning: transparent object has no getmatrix callback defined, ignoring.\n");
  }
}



/* Routine for rendering transparencies at the end of frame */

void mav_transparentObjectsRender(void)
{
  MAV_transObjData *to, *targ;
  float larg;
  int i, no, org_opt_trans;

  /* get number of transparent objects */
  no= mav_listSize(mav_transObjList);

  if (no) {

    /* store then turn off transparency trap while rendering list */
    org_opt_trans= mav_opt_trans;
    mav_opt_trans= 0;

    /* switch on blending and push the matrix */
    mav_gfxBlendSet(MAV_BLEND_1);
    mav_gfxMatrixPush();

    /* for each transparent object */
    for (i=0; i<no; i++) {

      /* find furthest object */
      larg= -1.0;
      targ= NULL;
      mav_listPointerReset(mav_transObjList);
      while (mav_listItemNext(mav_transObjList, (void **) &to)) {
	if (to->dist2>larg) {
	  targ= to;
	  larg= to->dist2;
	}
      }

      /* draw the object */
      mav_gfxMatrixLoad(targ->mat);
      (targ->fn)(targ->obj, targ->dip);
      
      /* remove item from list and free the memory used */
      mav_listItemRmv(mav_transObjList, targ);
      mav_free(targ);
    }

    /* switch off blending and pop the matrix */
    mav_gfxBlendSet(MAV_BLEND_OFF);
    mav_gfxMatrixPop();
    
    /* restore transparency trap again */
    mav_opt_trans= org_opt_trans;
  }
}



/* Routine to work out if an object is textured */

int mav_objectIsTextured(MAV_window *w, MAV_object *o)
{
  MAV_surfaceParams **sp;

  return (mav_callbackGetSurfaceParamsExec(w, o, &sp) && mav_surfaceParamsIsTextured(w, *sp));
}



/* Routine to deal with textured object (store and render later) */

void mav_texturedObjectsManage(MAV_object *o, MAV_drawInfo *di)
{
  MAV_texturedObjData *to= mav_malloc(sizeof(MAV_texturedObjData));
  MAV_surfaceParams **sp;

  /* store textured object */
  to->obj= o;
  to->fn= (MAV_callbackDrawFn) mav_callbackQuery(mav_callback_draw, mav_win_current, o);
  if (di) 
  {
    to->di= *di;
    to->dip= &to->di;
  }
  else 
  {
    to->dip= NULL;
  }

  /* store the pre-multiplying matrix */
  if (mav_opt_trackMatrix) 
  {
    to->mat= mav_win_current->viewMat;
  }
  else
  {
    to->mat= mav_gfxMatrixGet();
  }

  /* add to appropriate texture's object list */
  mav_callbackGetSurfaceParamsExec(mav_win_current, o, &sp);
  mav_listItemAdd(mav_textureObjList[(*sp)->texture], (void *) to);
}



/* Routine for rendering textured objects at the end of frame */

void mav_texturedObjectsRender(void)
{
  MAV_texturedObjData *targ;
  int i, org_opt_delayTexture;

  /* store then turn off texture trap while rendering lists */
  org_opt_delayTexture= mav_opt_delayTexture;
  mav_opt_delayTexture= 0;

  /* push the matrix */
  mav_gfxMatrixPush();

  /* for each texture */
  for (i=0; i<mav_opt_maxTextures; i++) {
    if (mav_listSize(mav_textureObjList[i])) {
      mav_listPointerReset(mav_textureObjList[i]);
      while (mav_listItemNext(mav_textureObjList[i], (void **)&targ)) {
	/* draw the object */
	mav_gfxMatrixLoad(targ->mat);
	(targ->fn)(targ->obj, targ->dip);
	mav_free(targ);
      }
       
      mav_listEmpty(mav_textureObjList[i]);
    }
  }
  
  /* pop the matrix */
  mav_gfxMatrixPop();
  
  /* restore texture trap again */
  mav_opt_delayTexture= org_opt_delayTexture;
}
