/***********************************************************************/
/* Open Visualization Data Explorer                                    */
/* (C) Copyright IBM Corp. 1989,1999                                   */
/* ALL RIGHTS RESERVED                                                 */
/* This code licensed under the                                        */
/*    "IBM PUBLIC LICENSE - Open Visualization Data Explorer"          */
/***********************************************************************/

#include <dxconfig.h>


#include "hwDeclarations.h"
#include "hwList.h"
#include "hwClipped.h"
#include "hwScreen.h"
#include "hwWindow.h"
#include "hwPortLayer.h"
#include "hwGather.h"
#include "hwView.h"
#include "hwMatrix.h"
#include "hwXfield.h"
#include "hwTmesh.h"
#include "hwMaterials.h"
#include "hwSort.h"

#include "hwDebug.h"


static Error 
_paintRecurse(void* globals, dxObject object,
	      gatherO gather, int buttonUp,
	      screenO parent, screenO grandparent);


struct processImageArg {
  void*	win;
  void* translation;
};

/*
 * Load lighting into the hardware. Provide surface properties to allow
 * the lights to be biased to represent surface properties.
 *
 * Some ports do not have a way to define a material properties that are
 * independent of the surface color (GL OpenGL). However, we can apply the
 * coeficients to the lights instead to achieve the same result
 *
 * Light at each vertex has three parts, an ambient, a diffuse and a specular
 * component. The vertex color is:
 * vert_color = amb_light_color * vert_color * k_amb +
 *	        for_each_light( light_color * vert_color * k_diff * misc_stuff +
 *                              light_color * k_spec * other_misc_stuff)
 *
 * if we wrap the k_amb and k_diff into the light color the equ becomes:
 *
 * vert_color = new_amb_light_color * vert_color +
 *	        for_each_light( new_light_color * vert_color *  misc_stuff +
 *                              new_light_color * k_spec/k_diff * other_misc_stuff)
 *
 * What is significant here is that we only need to send a single color to GL
 * for ambient,diffuse and specular, and that this color does not need to be
 * multiplied before being sent down. This is a big optimization.
 */
static Error
loadLights(void* globals, gatherO gather, materialAttrP newMaterial)
{
  DEFGLOBALDATA(globals);
  DEFPORT(PORT_HANDLE);
  int	i,j;
  Light	newLight;
  static materialAttrT	identMaterial = {1.0, 1.0, 1.0, 1};
  RGBColor color;
  listO lights;
  materialO omo, nmo;
  float gka, gkd, gks, gsp;

  ENTRY(("loadLights(0x%x, 0x%x, 0x%x)",globals, gather, newMaterial));

  /* 
   * if no material coeficients were specified or this port does not
   * want the coeficients pre-multiplied into the lights, use all
   * unit valued coeficients.
   */
  if(!newMaterial || 
     !_dxf_isFlagsSet(_dxf_SERVICES_FLAGS(),SF_CONST_COEF_HELP))
    newMaterial = &identMaterial;

  /*
   * Only re-load the lights if the material coeficients have changed
   */
  if (! _dxf_getHwGatherMaterial(gather, &omo))
      goto error;

  if (omo)
      if (! _dxf_getHwMaterialInfo(omo, &gka, &gkd, &gks, &gsp))
	  goto error;

  if(!omo ||
     gka != newMaterial->ambient ||
     gkd != newMaterial->diffuse) {

    nmo = _dxf_newHwMaterial(
		newMaterial->ambient,
		newMaterial->diffuse,
		newMaterial->specular,
		newMaterial->shininess);

    if (! nmo)
	goto error;
       
    if (! _dxf_setHwGatherMaterial(gather, nmo))
      goto error;

    if (! _dxf_getHwGatherAmbientColor(gather, &color))
      goto error;
    
    /* Load (or disable) the Ambient light */
    if(color.r == 0.0 && color.g == 0.0 && color.b == 0.0) 
    {

      if (!_dxf_DEFINE_LIGHT (LWIN, 0, NULL))
        goto error;

    }
    else
    {
	float		ambient = 1.0;

	/* 
	 * For ports with SF_CONST_COEF_HELP we put the ambient/diffuse coefs
	 * in the ambient light color. This is for GL, this allows us to
	 * use LMC_AD color for surfaces. This reduces the number of calls
	 * we need by 2 per vertex.
	 *
	 * The ambient is divided by the diffuse so that we can multiply the
	 * diffuse into the surface color and use LMC_AD for the color.
	 */

	if(newMaterial->diffuse)
	  ambient = newMaterial->ambient / newMaterial->diffuse;
	else
	  ambient = newMaterial->ambient / (1./10000.);

	color.r *= ambient;
	color.g *= ambient;
	color.b *= ambient;

	if(!(newLight = DXNewAmbientLight(color)))
	  goto error;

	if (!_dxf_DEFINE_LIGHT (LWIN, 0, newLight))
	  goto error;
	
	DXDelete((dxObject)newLight);
    }

    if (! _dxf_getHwGatherLights(gather, &lights))
	goto error;

    /* Load all directional lights */
    for(i=1,j=_dxf_listSize(lights)-1;j>=0 && i<=8;j--,i++) {
      if (!_dxf_DEFINE_LIGHT (LWIN, i, 
			      _dxf_listGetItem(lights,j)))
	goto error;
    }
    
    /* remove any previously defined lights, that have not been redefined */
    for(;i<=8;i++) {
      if (!_dxf_DEFINE_LIGHT (LWIN, i, NULL))
	goto error;
    }	
  }

  EXIT(("OK"));
  return OK;

 error:

  EXIT(("ERROR"));
  return ERROR;
}   

static Field
_processImage(Field f, char* args, int size)
{
  struct processImageArg* argI = (struct processImageArg*)args;
  DEFWINDATA((argI->win));
  DEFPORT(PORT_HANDLE);

  ENTRY(("_processImage(0x%x, \"%s\", %d)",f, args, size));

  /* XXX Will this work in parallel ? */
  _dxf_DRAW_IMAGE(argI->win, (dxObject)f, argI->translation);
  
  EXIT(("f = 0x%x",f));
  return f;
}

Error
_dxf_paint(void* globals, viewO view, int buttonUp, screenO camScreen)
{
  DEFGLOBALDATA(globals);
  DEFPORT(PORT_HANDLE);
  gatherO	gather;
  translationO  translation;
  int flags;
  dxObject obj;
  RGBColor color;
  RGBColor background;
  float projectionMat[4][4];
  float viewMat[4][4];
  SortListP slp;


  if (! _dxf_getHwViewInfo(view, NULL, &gather, &translation,
		(float *)&projectionMat, (float *)viewMat, &background))
      goto error;

  ENTRY(("_dxf_paint(0x%x, 0x%x, %d)",globals, view, buttonUp));

  /* Turn off trace macros from this level down */
  /* Turn it on by exporting this variable */
  DEBUG_CALL ( if (!getenv("DXHW_DEBUG_DETAIL")) DEBUG_OFF(); );

  if (! _dxf_getHwGatherFlags(gather, &flags))
      goto error;

  if(flags & CONTAINS_IMAGE_ONLY) {
    dxObject object;
    static RGBColor black = {0.0,0.0,0.0};

    _dxf_SET_BACKGROUND_COLOR(PORT_CTX,black);
    _dxf_INIT_RENDER_PASS (LWIN,SingleBufferMode,ZBufferDisabled);

    if (! _dxf_getHwGatherObject(gather, &object))
	goto error;


    _dxf_DRAW_IMAGE(LWIN, object, translation);

  }
  else
  {
    dxObject object;

    _dxf_REPLACE_VIEW_MATRIX(PORT_CTX,projectionMat);
    _dxf_PUSH_REPLACE_MATRIX(PORT_CTX,viewMat);
    _dxf_SET_BACKGROUND_COLOR(PORT_CTX,background);
    _dxf_INIT_RENDER_PASS (LWIN,DoubleBufferMode,ZBufferEnabled);
    
    /* force a reload of the lights with each new object */
    if (! _dxf_setHwGatherMaterial(gather, NULL))
	goto error;

    if (! _dxf_getHwGatherObject(gather, &object))
	goto error;

    if(!_paintRecurse(globals, object, gather, buttonUp, camScreen, NULL))
      goto error;

    slp = (SortListP)SORTLIST;
    if (slp->itemCount)
    {
	if (! _dxf_Sort_Translucent(SORTLIST))
	  goto error;
		
	if (! _dxf_getHwGatherAmbientColor(gather, &color))
	  goto error;

        if(!_dxf_DRAW_TRANSPARENT(globals, &color, buttonUp))
	  goto error;
    }

    _dxf_POP_MATRIX(PORT_CTX);
  }

 done:

  DEBUG_ON();
  EXIT(("OK"));
  return OK;

 error:

  DEBUG_ON();
  EXIT(("ERROR"));
  return ERROR;
}

static Error
_paintRecurse(void* globals, dxObject object,
	      gatherO gather, int buttonUp, 
	      screenO parent, screenO grandparent)
{
  DEFGLOBALDATA(globals);
  DEFPORT(PORT_HANDLE);
  xfieldP	xf;
  Class		class;
  int		i;
  dxObject	subObject;

  ENTRY(("_paintRecurse(0x%x, 0x%x, 0x%x, %d)",
	 globals, object, gather, buttonUp));
  
  if(!object)
  {
    EXIT(("object = NULL"));
    return OK;
  }

  if(!(class = DXGetObjectClass(object)))
     goto error;
  
  switch (class)
  {
  case CLASS_GROUP:
    PRINT(("CLASS_GROUP"));
    for (i=0; subObject=DXGetEnumeratedMember((Group)object, i, NULL); i++) {
      dxObject tmpObject;
      /* DXDebug("g", "member %d", i); */
      if (!_paintRecurse(globals, subObject, gather, buttonUp,
					parent, grandparent))
	goto error;
    }
    break;
  case CLASS_PRIVATE: /* created from CLASS_FIELD in gather */
    {
      switch(_dxf_getHwClass(object)) {
      case HW_CLASS_XFIELD:
      {
	  xfieldP	xf = _dxf_getXfieldData((xfieldO)object);
	  float		fuzz;
	  
	  PRINT(("HW_CLASS_XFIELD"));
	  if(_dxf_isFlagsSet(_dxf_attributeFlags(_dxf_xfieldAttributes(xf)),
			     IN_CLIP_OBJECT))
          {
	    /*
	     * The current implementation allows any object to be clip object,
	     * this line allows the beginClip to call back into this function
	     * to get the clip object recursively drawn
	     */
	    if (!_dxf_DRAW_CLIP(PORT_HANDLE, xf, buttonUp))
	      goto error;
	  }
	  else
	  {
	    int nClips;
	    Point *clipPts = NULL;
	    Vector *clipVecs = NULL;

	    if (! _dxf_getHwGatherClipPlanes(gather, &nClips, &clipPts, &clipVecs))
	      goto error;

	    if (nClips)
	    {
		if (! _dxf_setHwXfieldClipPlanes(object, nClips, clipPts, clipVecs))
		    goto error;
		
		DXFree((Pointer)clipPts);
		DXFree((Pointer)clipVecs);
	    }

	    if(!loadLights(globals, gather,
			   _dxf_attributeFrontMaterial(_dxf_xfieldAttributes(xf))))
	      goto error;


	    
	    /* XXXX
	       I hate this. Fuzz forces us to have the camera available when
	       we are drawing each field !
	       */
	    if(!_dxf_isFlagsSet(_dxf_attributeFlags(_dxf_xfieldAttributes(xf)),
				IN_SCREEN_OBJECT)
	       && (fuzz = (float)*_dxf_attributeFuzz(_dxf_xfieldAttributes(xf),
						     NULL)))
	    {
	      float	vm[4][4];
	      RGBColor color;

	      PRINT(("dealing with fuzz..."));
	      _dxfGetViewMatrixWithFuzz(MATRIX_STACK, (double)fuzz, vm);
	      _dxf_PUSH_REPLACE_MATRIX(PORT_CTX,vm);

	      if (! _dxf_getHwGatherAmbientColor(gather, &color))
		  goto error;

	      if(!_dxf_DRAW_OPAQUE(PORT_HANDLE, xf,
				      color, buttonUp))
		   goto error;

	      _dxf_POP_MATRIX(PORT_CTX);
	    }
	    else 
            {
	      RGBColor color;
	      PRINT(("! Fuzzy Screen Object"));

	      if (! _dxf_getHwGatherAmbientColor(gather, &color))
		  goto error;

	      if(!_dxf_DRAW_OPAQUE(PORT_HANDLE, xf,
				 color, buttonUp))
	      goto error;
	    }
	  }
	}
	break;
      case HW_CLASS_SCREEN:
	{
	  dxScreen		s;
	  float		       	vm[4][4],pm[4][4];

	  PRINT(("HW_CLASS_SCREEN:"));

	  if(!(_dxf_getHwScreenView((screenO)object, &s,
			&subObject,vm,NULL, NULL,NULL)))
	    goto error;

	  if (_dxf_getHwClass(parent) != HW_CLASS_SCREEN)
	  {
	    DXSetError(ERROR_INTERNAL, "parent of screen not screen?");
	    goto error;
	  }

	  if(!(_dxf_getHwScreenView((screenO)parent, NULL,
			NULL,NULL,pm, NULL,NULL)))
	    goto error;

	  /*
	   * If this is a top level screen object (its parent was created from
	   * the camera and has no parent), then set the projection matix to
	   * identity. Reset the the camera projection matrix on exit.
	   */
	  if (!grandparent)
	    _dxf_REPLACE_VIEW_MATRIX(PORT_CTX,(float (*)[4])identity);

	  _dxf_PUSH_REPLACE_MATRIX(PORT_CTX,vm);

	  if (!_paintRecurse(globals, subObject, gather,
					buttonUp, object, parent))
	    goto error;
	  
	  _dxf_POP_MATRIX(PORT_CTX);

	  if(!grandparent)
	    _dxf_REPLACE_VIEW_MATRIX(PORT_CTX,pm);
	}
	break;
      case HW_CLASS_CLIPPED:
	{
	  int		knt;
	  Point         *pts;
	  Vector        *nrms;
	  dxObject      subObject;
	  int		i;
	  
	  PRINT(("HW_CLASS_CLIPPED:"));
	  
	  /*
	   * pt and normal are in world coordinates. _dxf_ADD_CLIP_PLANE
	   * requires view coordinates.
	   *
	   * So we need to apply the inverse of the clipping transform to the 
	   * pt and normal to correct for this.
	   */

	  if (!_dxf_getHwClippedInfo((clippedO)object,
					&pts, &nrms, &knt, &subObject))
	    goto error;
	
	  if (!_dxf_pushHwGatherClipPlanes(gather, knt, pts, nrms))
	    goto error;
	
	  if (!_dxf_ADD_CLIP_PLANES(LWIN, pts, nrms, knt))
	    goto error;

	  if(!_paintRecurse(globals, subObject, gather, buttonUp,
			parent, grandparent))
	    goto error;
	  
	  if (!_dxf_popHwGatherClipPlanes(gather))
	      goto error;

	  if (!_dxf_REMOVE_CLIP_PLANES(LWIN, knt))
	    goto error;
	}
	break;
      default:
	DXErrorGoto (ERROR_BAD_PARAMETER,
		     "Unrecognized private Object during paint");
	break;
      }
    }
    break;
  case CLASS_CLIPPED:
  case CLASS_XFORM:
  case CLASS_SCREEN:
  case CLASS_FIELD:
  case CLASS_LIGHT:
  default:
    DXSetError (ERROR_BAD_PARAMETER,
		"Unrecognized Object (%d) during paint",class);
    goto error;
    break;
  }
  
  EXIT(("OK"));
  return OK;

error:

  EXIT(("ERROR"));
  return ERROR;
}



