/****************************************************************************
*                pigment.c
*
*  This module implements solid texturing functions that modify the color
*  transparency of an object's surface.
*
*  from Persistence of Vision(tm) Ray Tracer
*  Copyright 1996 Persistence of Vision Team
*---------------------------------------------------------------------------
*  NOTICE: This source code file is provided so that users may experiment
*  with enhancements to POV-Ray and to port the software to platforms other 
*  than those supported by the POV-Ray Team.  There are strict rules under
*  which you are permitted to use this file.  The rules are in the file
*  named POVLEGAL.DOC which should be distributed with this file. If 
*  POVLEGAL.DOC is not available or for more info please contact the POV-Ray
*  Team Coordinator by leaving a message in CompuServe's Graphics Developer's
*  Forum.  The latest version of POV-Ray may be found there as well.
*
* This program is based on the popular DKB raytracer version 2.12.
* DKBTrace was originally written by David K. Buck.
* DKBTrace Ver 2.0-2.12 were written by David K. Buck & Aaron A. Collins.
*
*****************************************************************************/

/*
   Some texture ideas garnered from SIGGRAPH '85 Volume 19 Number 3, 
   "An Image Synthesizer" By Ken Perlin.
   Further Ideas Garnered from "The RenderMan Companion" (Addison Wesley).
*/

#include "frame.h"
#include "vector.h"
#include "povproto.h"
#include "texture.h"
#include "colour.h"   
#include "image.h"    
#include "matrices.h" 
#include "pigment.h"  
#include "txttest.h"
                    

/*****************************************************************************
* Local preprocessor defines
******************************************************************************/



/*****************************************************************************
* Local typedefs
******************************************************************************/



/*****************************************************************************
* Local variables
******************************************************************************/

static BLEND_MAP_ENTRY Black_White_Entries[2] /* =
  {{0.0, FALSE, {{0.0, 0.0, 0.0, 0.0, 0.0}}},
  {1.0, FALSE, {{1.0, 1.0, 1.0, 0.0, 0.0}}}} */ ;

static BLEND_MAP Gray_Default_Map =
  { 2,  FALSE, COLOUR_TYPE,  -1,  Black_White_Entries};

static BLEND_MAP_ENTRY Bozo_Entries[6] /* =
  {{0.4, FALSE, {{1.0, 1.0, 1.0, 0.0, 0.0}}},
   {0.4, FALSE, {{0.0, 1.0, 0.0, 0.0, 0.0}}},
   {0.6, FALSE, {{0.0, 1.0, 0.0, 0.0, 0.0}}},
   {0.6, FALSE, {{0.0, 0.0, 1.0, 0.0, 0.0}}},
   {0.8, FALSE, {{0.0, 0.0, 1.0, 0.0, 0.0}}},
   {0.8, FALSE, {{1.0, 0.0, 0.0, 0.0, 0.0}}}} */ ;

static BLEND_MAP Bozo_Default_Map =
  { 6,  FALSE, COLOUR_TYPE,  -1,  Bozo_Entries};

static BLEND_MAP_ENTRY Wood_Entries[2] /* =
  {{0.6, FALSE, {{0.666, 0.312,  0.2,   0.0, 0.0}}},
   {0.6, FALSE, {{0.4,   0.1333, 0.066, 0.0, 0.0}}}} */ ;
    
static BLEND_MAP Wood_Default_Map =
  { 2,  FALSE, COLOUR_TYPE,  -1,  Wood_Entries};

static BLEND_MAP_ENTRY Mandel_Entries[5] /* =
  {{0.001, FALSE, {{0.0, 0.0, 0.0, 0.0, 0.0}}},
   {0.001, FALSE, {{0.0, 1.0, 1.0, 0.0, 0.0}}},
   {0.012, FALSE, {{1.0, 1.0, 0.0, 0.0, 0.0}}},
   {0.015, FALSE, {{1.0, 0.0, 1.0, 0.0, 0.0}}},
   {0.1,   FALSE, {{0.0, 1.0, 1.0, 0.0, 0.0}}}} */ ;

static BLEND_MAP Mandel_Default_Map =
  { 5,  FALSE, COLOUR_TYPE,  -1,  Mandel_Entries};

static BLEND_MAP_ENTRY Agate_Entries[6] /* =
  {{0.0, FALSE, {{1.0,  1.0,  1.0,  0.0, 0.0}}},
   {0.5, FALSE, {{0.95, 0.75, 0.5,  0.0, 0.0}}},
   {0.5, FALSE, {{0.9,  0.7,  0.5,  0.0, 0.0}}},
   {0.6, FALSE, {{0.9,  0.7,  0.4,  0.0, 0.0}}},
   {0.6, FALSE, {{1.0,  0.7,  0.4,  0.0, 0.0}}},
   {1.0, FALSE, {{0.6,  0.3,  0.0,  0.0, 0.0}}}} */ ;

static BLEND_MAP Agate_Default_Map =
  { 6,  FALSE, COLOUR_TYPE,  -1,  Agate_Entries};

static BLEND_MAP_ENTRY Radial_Entries[4] /* =
  {{0.0,   FALSE, {{0.0, 1.0, 1.0, 0.0, 0.0}}},
   {0.333, FALSE, {{1.0, 1.0, 0.0, 0.0, 0.0}}},
   {0.666, FALSE, {{1.0, 0.0, 1.0, 0.0, 0.0}}},
   {1.0,   FALSE, {{0.0, 1.0, 1.0, 0.0, 0.0}}}} */ ;

static BLEND_MAP Radial_Default_Map =
  { 4,  FALSE, COLOUR_TYPE,  -1,  Radial_Entries};

static BLEND_MAP_ENTRY Marble_Entries[3] /* =
  {{0.0, FALSE, {{0.9, 0.8,  0.8,  0.0, 0.0}}},
   {0.9, FALSE, {{0.9, 0.08, 0.08, 0.0, 0.0}}},
   {0.9, FALSE, {{0.0, 0.0, 0.0, 0.0, 0.0}}}} */ ;

static BLEND_MAP Marble_Default_Map =
  { 3,  FALSE, COLOUR_TYPE,  -1,  Marble_Entries};

static BLEND_MAP_ENTRY Brick_Entries[2] /* =
  {{0.0, FALSE, {{0.5, 0.5,  0.5,  0.0, 0.0}}},
   {1.0, FALSE, {{0.6, 0.15, 0.15, 0.0, 0.0}}}} */ ;

BLEND_MAP Brick_Default_Map =
  { 2,  FALSE, COLOUR_TYPE,  -1,  Brick_Entries};

static BLEND_MAP_ENTRY Hex_Entries[3] /*=
  {{0.0, FALSE, {{0.0, 0.0, 1.0, 0.0, 0.0}}},
   {1.0, FALSE, {{0.0, 1.0, 0.0, 0.0, 0.0}}},
   {2.0, FALSE, {{1.0, 0.0, 0.0, 0.0, 0.0}}}} */;

BLEND_MAP Hex_Default_Map =
  { 3, FALSE,COLOUR_TYPE, -1, Hex_Entries};

BLEND_MAP Check_Default_Map =
  { 2, FALSE,COLOUR_TYPE, -1, Hex_Entries}; /* Yes... Hex_Entries, not Check [CY] */



/*****************************************************************************
* Static functions
******************************************************************************/
static void Do_Average_Pigments PARAMS((COLOUR Colour, PIGMENT *Pigment, VECTOR EPoint));



/*****************************************************************************
*
* FUNCTION
*
*   Create_Pigment
*
* INPUT
*   
* OUTPUT
*   
* RETURNS
*
*   pointer to the created pigment
*   
* AUTHOR
*
*   POV-Ray Team
*   
* DESCRIPTION   : Allocate memory for new pigment and initialize it to
*                 system default values.
*
* CHANGES
*
******************************************************************************/

PIGMENT *Create_Pigment ()
{
  PIGMENT *New;

  New = (PIGMENT *)POV_MALLOC(sizeof (PIGMENT), "pigment");

  Init_TPat_Fields((TPATTERN *)New);

  Make_Colour(New->Colour, 0.0,0.0,0.0) ;

  New->Blend_Map = NULL;

  return (New);
}



/*****************************************************************************
*
* FUNCTION
*
*   Copy_Pigment
*
* INPUT
*
*   Old -- point to pigment to be copied
*   
* RETURNS
*
*   pointer to the created pigment
*   
* AUTHOR
*
*   POV-Ray Team
*   
* DESCRIPTION   : Allocate memory for new pigment and initialize it to
*                 values in existing pigment Old.
*
* CHANGES
*
******************************************************************************/

PIGMENT *Copy_Pigment (Old)
PIGMENT *Old;
{
  PIGMENT *New;

  if (Old != NULL)
  {
    New = Create_Pigment ();

    Copy_TPat_Fields ((TPATTERN *)New, (TPATTERN *)Old);

    if (Old->Type == PLAIN_PATTERN)
    {
      Assign_Colour(New->Colour,Old->Colour);
    }
  }
  else
  {
    New = NULL;
  }

  return (New);
}



/*****************************************************************************
*
* FUNCTION
*
*   Destroy_Pigment
*
* INPUT
*
*   pointer to pigment to destroied
*   
* OUTPUT
*   
* RETURNS
*   
* AUTHOR
*
*   POV-Ray Team
*   
* DESCRIPTION   : free all memory associated with given pigment
*
* CHANGES
*
******************************************************************************/

void Destroy_Pigment (Pigment)
PIGMENT *Pigment;
{
  if (Pigment != NULL)
  {
    Destroy_Pigment((PIGMENT *)Pigment->Next);

    Destroy_TPat_Fields ((TPATTERN *)Pigment);

    POV_FREE(Pigment);
  }
}



/*****************************************************************************
*
* FUNCTION
*
*   Post_Pigment
*
* INPUT
*   
* OUTPUT
*   
* RETURNS
*   
* AUTHOR
*
*   Chris Young
*   
* DESCRIPTION
*
* CHANGES
*
******************************************************************************/

int Post_Pigment(Pigment)
PIGMENT *Pigment;
{
  int i, Has_Filter;
  BLEND_MAP *Map;

  if (Pigment == NULL)
  {
    Error("Missing pigment");
  }

  if (Pigment->Flags & POST_DONE)
  {
    return(Pigment->Flags & HAS_FILTER);
  }

  if (Pigment->Type == NO_PATTERN)
  {
    Pigment->Type = PLAIN_PATTERN;

    Make_Colour(Pigment->Colour, 0.0, 0.0, 0.0) ;

    Warning(1.5, "No pigment type given.\n");
  }

  Pigment->Flags |= POST_DONE;

  switch (Pigment->Type)
  {
    case PLAIN_PATTERN:

      Destroy_Warps (Pigment->Warps);

      Pigment->Warps = NULL;

      break;

    case NO_PATTERN:
    case BITMAP_PATTERN:

      break;

    default:

      if (Pigment->Blend_Map == NULL)
      {
        switch (Pigment->Type)
        {
          case BOZO_PATTERN:    Pigment->Blend_Map = &Bozo_Default_Map;  break;
          case BRICK_PATTERN:   Pigment->Blend_Map = &Brick_Default_Map; break;
          case WOOD_PATTERN:    Pigment->Blend_Map = &Wood_Default_Map;  break;
          case MANDEL_PATTERN:  Pigment->Blend_Map = &Mandel_Default_Map;break;
          case RADIAL_PATTERN:  Pigment->Blend_Map = &Radial_Default_Map;break;
          case AGATE_PATTERN:   Pigment->Blend_Map = &Agate_Default_Map; break;
          case MARBLE_PATTERN:  Pigment->Blend_Map = &Marble_Default_Map;break;
          case HEXAGON_PATTERN: Pigment->Blend_Map = &Hex_Default_Map;   break;
          case CHECKER_PATTERN: Pigment->Blend_Map = &Check_Default_Map; break;
          case AVERAGE_PATTERN: Error("Missing pigment_map in average pigment"); break;
          default:              Pigment->Blend_Map = &Gray_Default_Map;  break;
        }
      }

      break;
  }

  /* Now we test wether this pigment is opaque or not. [DB 8/94] */

  Has_Filter = FALSE;

  if ((fabs(Pigment->Colour[FILTER]) > EPSILON) ||
      (fabs(Pigment->Colour[TRANSM]) > EPSILON))
  {
    Has_Filter = TRUE;
  }

  if ((Map = Pigment->Blend_Map) != NULL)
  {
    if (Map->Type == PIGMENT_TYPE)
    {
       for (i = 0; i < Map->Number_Of_Entries; i++)
       {
         Has_Filter |= Post_Pigment(Map->Blend_Map_Entries[i].Vals.Pigment);
       }
    }
    else
    {
       for (i = 0; i < Map->Number_Of_Entries; i++)
       {
         Has_Filter |= fabs(Map->Blend_Map_Entries[i].Vals.Colour[FILTER])>EPSILON;
         Has_Filter |= fabs(Map->Blend_Map_Entries[i].Vals.Colour[TRANSM])>EPSILON;
       }
    }
  }

  if (Has_Filter)
  {
    Pigment->Flags |= HAS_FILTER;
  }

  return(Has_Filter);
}



/*****************************************************************************
*
* FUNCTION
*
*   Compute_Pigment
*
* INPUT
*
*   Pigment - Info about this pigment
*   EPoint  - 3-D point at which pattern is evaluated
*
* OUTPUT
*
*   Colour  - Resulting color is returned here.
*
* RETURNS
*
*   int - TRUE,  if a color was found for the given point
*         FALSE, if no color was found (e.g. areas outside an image map
*                that has the once option)
*
* AUTHOR
*
*   POV-Ray Team
*
* DESCRIPTION
*   Given a 3d point and a pigment, compute colour from that layer.
*   (Formerly called "Colour_At", or "Add_Pigment")
*
* CHANGES
*   Added pigment map support [CY 11/94]
*
******************************************************************************/

int Compute_Pigment (Colour, Pigment, EPoint)
COLOUR Colour;
PIGMENT *Pigment;
VECTOR EPoint;
{
  int Colour_Found;
  VECTOR TPoint;
  DBL value;
  register DBL fraction;
  BLEND_MAP_ENTRY *Cur, *Prev;
  COLOUR Temp_Colour;
  BLEND_MAP *Blend_Map = Pigment->Blend_Map;

  if (Pigment->Type <= LAST_SPECIAL_PATTERN)
  {
    Colour_Found = TRUE;

    switch (Pigment->Type)
    {
      case NO_PATTERN:

        Make_Colour(Colour, 0.0, 0.0, 0.0);

        break;

      case PLAIN_PATTERN:

        Assign_Colour(Colour,Pigment->Colour);

        break;

      case AVERAGE_PATTERN:

        Warp_EPoint (TPoint, EPoint, (TPATTERN *)Pigment);

        Do_Average_Pigments(Colour,Pigment,TPoint);

        break;

      case BITMAP_PATTERN:

        Warp_EPoint (TPoint, EPoint, (TPATTERN *)Pigment);

        Make_Colour(Colour, 0.0, 0.0, 0.0);

        Colour_Found = image_map (TPoint, Pigment, Colour);

        break;

      default:

        Error("Pigment type %d not yet implemented",Pigment->Type);
    }

    return(Colour_Found);
  }

  Colour_Found = FALSE;

  value = Evaluate_TPat ((TPATTERN *)Pigment,EPoint);

  Search_Blend_Map (value, Blend_Map, &Prev, &Cur);

  if (Blend_Map->Type == COLOUR_TYPE)
  {
    Colour_Found = TRUE;

    Assign_Colour(Colour, Cur->Vals.Colour);
  }
  else
  {
    Warp_EPoint (TPoint, EPoint, (TPATTERN *)Pigment);

    if (Compute_Pigment(Colour, Cur->Vals.Pigment,TPoint))
    {
      Colour_Found = TRUE;
    }
  }

  if (Prev != Cur)
  {
    if (Blend_Map->Type == COLOUR_TYPE)
    {
      Colour_Found = TRUE;

      Assign_Colour(Temp_Colour, Prev->Vals.Colour);
    }
    else
    {
      if (Compute_Pigment(Temp_Colour, Prev->Vals.Pigment, TPoint))
      {
        Colour_Found = TRUE;
      }
    }

    fraction = (value - Prev->value) / (Cur->value - Prev->value);

    Colour[RED]    = Temp_Colour[RED]    + fraction * (Colour[RED]    - Temp_Colour[RED]);
    Colour[GREEN]  = Temp_Colour[GREEN]  + fraction * (Colour[GREEN]  - Temp_Colour[GREEN]);
    Colour[BLUE]   = Temp_Colour[BLUE]   + fraction * (Colour[BLUE]   - Temp_Colour[BLUE]);
    Colour[FILTER] = Temp_Colour[FILTER] + fraction * (Colour[FILTER] - Temp_Colour[FILTER]);
    Colour[TRANSM] = Temp_Colour[TRANSM] + fraction * (Colour[TRANSM] - Temp_Colour[TRANSM]);
  }

  return(Colour_Found);
}



/*****************************************************************************
*
* FUNCTION
*
* INPUT
*
* OUTPUT
*   
* RETURNS
*   
* AUTHOR
*   
* DESCRIPTION
*
* CHANGES
*
******************************************************************************/

static void Do_Average_Pigments (Colour, Pigment, EPoint)
COLOUR Colour;
PIGMENT *Pigment;
VECTOR EPoint;
{
   int i;
   COLOUR LC;
   BLEND_MAP *Map = Pigment->Blend_Map;
   SNGL Value;
   SNGL Total = 0.0;
   
   Make_Colour (Colour, 0.0, 0.0, 0.0);

   for (i = 0; i < Map->Number_Of_Entries; i++)
   {
     Value = Map->Blend_Map_Entries[i].value;

     Compute_Pigment (LC,Map->Blend_Map_Entries[i].Vals.Pigment,EPoint);
     
     Colour[RED]   += LC[RED]   *Value;
     Colour[GREEN] += LC[GREEN] *Value;
     Colour[BLUE]  += LC[BLUE]  *Value;
     Colour[FILTER]+= LC[FILTER]*Value;
     Colour[TRANSM]+= LC[TRANSM]*Value;

     Total += Value;
   }
   Colour[RED]   /= Total;
   Colour[GREEN] /= Total;
   Colour[BLUE]  /= Total;
   Colour[FILTER]/= Total;
   Colour[TRANSM]/= Total;
}



/*****************************************************************************
*
* FUNCTION  Make_Pigment_Entries
*
* INPUT  None
*   
* OUTPUT  Initializes default pigment blend_map values.
*   
* RETURNS  None
*   
* AUTHOR  Steve Demlow, Dec. '95
*   
* DESCRIPTION  Some pre-ANSI compilers won't auto-initialize unions, so these
*   have to be done in regular code.
*
* CHANGES
*
******************************************************************************/

void Make_Pigment_Entries()
{
  static unsigned char Made = FALSE;

  if (Made) {
    return;
  }
  Made = TRUE;

  Make_Blend_Map_Entry(Black_White_Entries[0] , 0.0, FALSE, 0.0, 0.0, 0.0, 0.0, 0.0);
  Make_Blend_Map_Entry(Black_White_Entries[1] , 1.0, FALSE, 1.0, 1.0, 1.0, 0.0, 0.0);
  
  Make_Blend_Map_Entry(Bozo_Entries[0], 0.4, FALSE, 1.0, 1.0, 1.0, 0.0, 0.0);
  Make_Blend_Map_Entry(Bozo_Entries[1], 0.4, FALSE, 0.0, 1.0, 0.0, 0.0, 0.0);
  Make_Blend_Map_Entry(Bozo_Entries[2], 0.6, FALSE, 0.0, 1.0, 0.0, 0.0, 0.0);
  Make_Blend_Map_Entry(Bozo_Entries[3], 0.6, FALSE, 0.0, 0.0, 1.0, 0.0, 0.0);
  Make_Blend_Map_Entry(Bozo_Entries[4], 0.8, FALSE, 0.0, 0.0, 1.0, 0.0, 0.0);
  Make_Blend_Map_Entry(Bozo_Entries[5], 0.8, FALSE, 1.0, 0.0, 0.0, 0.0, 0.0);
  
  Make_Blend_Map_Entry(Wood_Entries[0], 0.6, FALSE, 0.666, 0.312,  0.2,   0.0, 0.0);
  Make_Blend_Map_Entry(Wood_Entries[1], 0.6, FALSE, 0.4,   0.1333, 0.066, 0.0, 0.0);
  
  Make_Blend_Map_Entry(Mandel_Entries[0], 0.001, FALSE, 0.0, 0.0, 0.0, 0.0, 0.0);
  Make_Blend_Map_Entry(Mandel_Entries[1], 0.001, FALSE, 0.0, 1.0, 1.0, 0.0, 0.0);
  Make_Blend_Map_Entry(Mandel_Entries[2], 0.012, FALSE, 1.0, 1.0, 0.0, 0.0, 0.0);
  Make_Blend_Map_Entry(Mandel_Entries[3], 0.015, FALSE, 1.0, 0.0, 1.0, 0.0, 0.0);
  Make_Blend_Map_Entry(Mandel_Entries[4], 0.1,   FALSE, 0.0, 1.0, 1.0, 0.0, 0.0);
  
  Make_Blend_Map_Entry(Agate_Entries[0], 0.0, FALSE, 1.0,  1.0,  1.0,  0.0, 0.0);
  Make_Blend_Map_Entry(Agate_Entries[1], 0.5, FALSE, 0.95, 0.75, 0.5,  0.0, 0.0);
  Make_Blend_Map_Entry(Agate_Entries[2], 0.5, FALSE, 0.9,  0.7,  0.5,  0.0, 0.0);
  Make_Blend_Map_Entry(Agate_Entries[3], 0.6, FALSE, 0.9,  0.7,  0.4,  0.0, 0.0);
  Make_Blend_Map_Entry(Agate_Entries[4], 0.6, FALSE, 1.0,  0.7,  0.4,  0.0, 0.0);
  Make_Blend_Map_Entry(Agate_Entries[5], 1.0, FALSE, 0.6,  0.3,  0.0,  0.0, 0.0);
  
  Make_Blend_Map_Entry(Radial_Entries[0], 0.0,   FALSE, 0.0, 1.0, 1.0, 0.0, 0.0);
  Make_Blend_Map_Entry(Radial_Entries[1], 0.333, FALSE, 1.0, 1.0, 0.0, 0.0, 0.0);
  Make_Blend_Map_Entry(Radial_Entries[2], 0.666, FALSE, 1.0, 0.0, 1.0, 0.0, 0.0);
  Make_Blend_Map_Entry(Radial_Entries[3], 1.0,   FALSE, 0.0, 1.0, 1.0, 0.0, 0.0);
  
  Make_Blend_Map_Entry(Marble_Entries[0], 0.0, FALSE, 0.9, 0.8, 0.8, 0.0, 0.0);
  Make_Blend_Map_Entry(Marble_Entries[1], 0.9, FALSE, 0.9, 0.08, 0.08, 0.0, 0.0);
  Make_Blend_Map_Entry(Marble_Entries[2], 0.9, FALSE, 0.0, 0.0, 0.0, 0.0, 0.0);
  
  Make_Blend_Map_Entry(Brick_Entries[0], 0.0, FALSE, 0.5, 0.5,  0.5,  0.0, 0.0);
  Make_Blend_Map_Entry(Brick_Entries[1], 1.0, FALSE, 0.6, 0.15, 0.15, 0.0, 0.0);
  
  Make_Blend_Map_Entry(Hex_Entries[0], 0.0, FALSE, 0.0, 0.0, 1.0, 0.0, 0.0);
  Make_Blend_Map_Entry(Hex_Entries[1], 1.0, FALSE, 0.0, 1.0, 0.0, 0.0, 0.0);
  Make_Blend_Map_Entry(Hex_Entries[2], 2.0, FALSE, 1.0, 0.0, 0.0, 0.0, 0.0);
}



