/******************************************************************************
Plush Version 1.2
mat.c
Material Control
Copyright (c) 1996-2000, Justin Frankel
******************************************************************************/

#include "plush.h"

static void _plGenerateSinglePalette(pl_Mat *);
static void _plGeneratePhongPalette(pl_Mat *);
static void _plGenerateTextureEnvPalette(pl_Mat *);
static void _plGenerateTexturePalette(pl_Mat *, pl_Texture *);
static void _plGeneratePhongTexturePalette(pl_Mat *, pl_Texture *);
static void _plGeneratePhongTransparentPalette(pl_Mat *m);
static void  _plGenerateTransparentPalette(pl_Mat *);
static void _plSetMaterialPutFace(pl_Mat *m);
static void _plMatSetupTransparent(pl_Mat *m, pl_uChar *pal);

pl_Mat *plMatCreate() {
  pl_Mat *m;
  m = (pl_Mat *) malloc(sizeof(pl_Mat));
  if (!m) return 0;
  memset(m,0,sizeof(pl_Mat));
  m->EnvScaling = 1.0f;
  m->TexScaling = 1.0f;
  m->Ambient[0] = m->Ambient[1] = m->Ambient[2] = 0;
  m->Diffuse[0] = m->Diffuse[1] = m->Diffuse[2] = 128;
  m->Specular[0] = m->Specular[1] = m->Specular[2] = 128;
  m->Shininess = 4;
  m->NumGradients = 32;
  m->FadeDist = 1000.0;
  m->zBufferable = 1;
  return m;
}

void plMatDelete(pl_Mat *m) {
  if (m) {
    if (m->_ReMapTable) free(m->_ReMapTable);
    if (m->_RequestedColors) free(m->_RequestedColors);
    if (m->_AddTable) free(m->_AddTable);
    free(m);
  }
}

void plMatInit(pl_Mat *m) {
  if (m->Shininess < 1) m->Shininess = 1;
  m->_ft = ((m->Environment ? PL_FILL_ENVIRONMENT : 0) |
           (m->Texture ? PL_FILL_TEXTURE : 0));
  m->_st = m->ShadeType;

  if (m->Transparent) m->_ft = PL_FILL_TRANSPARENT;

  if (m->_ft == (PL_FILL_TEXTURE|PL_FILL_ENVIRONMENT))
    m->_st = PL_SHADE_NONE;

  if (m->_ft == PL_FILL_SOLID) {
    if (m->_st == PL_SHADE_NONE) _plGenerateSinglePalette(m);
    else _plGeneratePhongPalette(m);
  } else if (m->_ft == PL_FILL_TEXTURE) {
    if (m->_st == PL_SHADE_NONE)
      _plGenerateTexturePalette(m,m->Texture);
    else _plGeneratePhongTexturePalette(m,m->Texture);
  } else if (m->_ft == PL_FILL_ENVIRONMENT) {
    if (m->_st == PL_SHADE_NONE)
      _plGenerateTexturePalette(m,m->Environment);
    else _plGeneratePhongTexturePalette(m,m->Environment);
  } else if (m->_ft == (PL_FILL_ENVIRONMENT|PL_FILL_TEXTURE))
    _plGenerateTextureEnvPalette(m);
  else if (m->_ft == PL_FILL_TRANSPARENT) {
    if (m->_st == PL_SHADE_NONE) _plGenerateTransparentPalette(m);
    else _plGeneratePhongTransparentPalette(m);
  }
  _plSetMaterialPutFace(m);
}

static void _plMatSetupTransparent(pl_Mat *m, pl_uChar *pal) {
  pl_uInt x, intensity;
  if (m->Transparent)
  {
    if (m->_AddTable) free(m->_AddTable);
    m->_AddTable = (pl_uInt16 *) malloc(256*sizeof(pl_uInt16));
    for (x = 0; x < 256; x ++) {
      intensity = *pal++;
      intensity += *pal++;
      intensity += *pal++;
      m->_AddTable[x] = ((intensity*(m->_ColorsUsed-m->_tsfact))/768);
    }
  }
}

void plMatMapToPal(pl_Mat *m, pl_uChar *pal, pl_sInt pstart, pl_sInt pend) {
  pl_sInt32 j, r, g, b, bestdiff, r2, g2, b2;
  pl_sInt bestpos,k;
  pl_uInt32 i;
  pl_uChar *p;
  if (!m->_RequestedColors) plMatInit(m);
  if (!m->_RequestedColors) return;
  if (m->_ReMapTable) free(m->_ReMapTable);
  m->_ReMapTable = (pl_uChar *) malloc(m->_ColorsUsed);
  for (i = 0; i < m->_ColorsUsed; i ++) {
    bestdiff = 1000000000;
    bestpos = pstart;
    r = m->_RequestedColors[i*3];
    g = m->_RequestedColors[i*3+1];
    b = m->_RequestedColors[i*3+2];
    p = pal + pstart*3;
    for (k = pstart; k <= (pl_sInt)pend; k ++) {
      r2 = p[0] - r;
      g2 = p[1] - g;
      b2 = p[2] - b;
      p += 3;
      j = r2*r2+g2*g2+b2*b2;
      if (j < bestdiff) {
        bestdiff = j;
        bestpos = k;
      }
    }
    m->_ReMapTable[i] = bestpos;
  }
  _plMatSetupTransparent(m,pal);
}

static void _plGenerateSinglePalette(pl_Mat *m) {
  m->_ColorsUsed = 1;
  if (m->_RequestedColors) free(m->_RequestedColors);
  m->_RequestedColors = (pl_uChar *) malloc(3);
  m->_RequestedColors[0] = plMin(plMax(m->Ambient[0],0),255);
  m->_RequestedColors[1] = plMin(plMax(m->Ambient[1],0),255);
  m->_RequestedColors[2] = plMin(plMax(m->Ambient[2],0),255);
}

static void _plGeneratePhongPalette(pl_Mat *m) {
  pl_uInt i = m->NumGradients, x;
  pl_sInt c;
  pl_uChar *pal;
  double a, da, ca, cb;
  m->_ColorsUsed = m->NumGradients;
  if (m->_RequestedColors) free(m->_RequestedColors);
  pal =  m->_RequestedColors = (pl_uChar *) malloc(m->_ColorsUsed*3);
  a = PL_PI/2.0;

  if (m->NumGradients > 1) da = -PL_PI/((m->NumGradients-1)<<1);
  else da=0.0;

  do {
    if (m->NumGradients == 1) ca = 1;
    else {
      ca = cos((double) a);
      a += da;
    }
    cb = pow((double) ca, (double) m->Shininess);
    for (x = 0; x < 3; x ++) {
      c = (pl_sInt) ((cb*m->Specular[x])+(ca*m->Diffuse[x])+m->Ambient[x]);
      *(pal++) = plMax(0,plMin(c,255));
    }
  } while (--i);
}

static void _plGenerateTextureEnvPalette(pl_Mat *m) {
  pl_sInt c;
  pl_uInt whichlevel,whichindex;
  pl_uChar *texpal, *envpal, *pal;
  m->_ColorsUsed = m->Texture->NumColors*m->Environment->NumColors;
  if (m->_RequestedColors) free(m->_RequestedColors);
  pal = m->_RequestedColors = (pl_uChar *) malloc(m->_ColorsUsed*3);
  envpal = m->Environment->PaletteData;
  if (m->_AddTable) free(m->_AddTable);
  m->_AddTable = (pl_uInt16 *) malloc(m->Environment->NumColors*sizeof(pl_uInt16));
  for (whichlevel = 0; whichlevel < m->Environment->NumColors; whichlevel++) {
    texpal = m->Texture->PaletteData;
    switch (m->TexEnvMode)
    {
      case PL_TEXENV_MUL: // multiply
        for (whichindex = 0; whichindex < m->Texture->NumColors; whichindex++) {
          *pal++ = (pl_uChar) (((pl_sInt) (*texpal++) * (pl_sInt) envpal[0])>>8);
          *pal++ = (pl_uChar) (((pl_sInt) (*texpal++) * (pl_sInt) envpal[1])>>8);
          *pal++ = (pl_uChar) (((pl_sInt) (*texpal++) * (pl_sInt) envpal[2])>>8);
        }
      break;
      case PL_TEXENV_AVG: // average
        for (whichindex = 0; whichindex < m->Texture->NumColors; whichindex++) {
          *pal++ = (pl_uChar) (((pl_sInt) (*texpal++) + (pl_sInt) envpal[0])>>1);
          *pal++ = (pl_uChar) (((pl_sInt) (*texpal++) + (pl_sInt) envpal[1])>>1);
          *pal++ = (pl_uChar) (((pl_sInt) (*texpal++) + (pl_sInt) envpal[2])>>1);
        }
      break;
      case PL_TEXENV_TEXMINUSENV: // tex-env
        for (whichindex = 0; whichindex < m->Texture->NumColors; whichindex++) {
          c = (pl_sInt) (*texpal++) - (pl_sInt) envpal[0]; *pal++ = plMax(0,plMin(255,c));
          c = (pl_sInt) (*texpal++) - (pl_sInt) envpal[1]; *pal++ = plMax(0,plMin(255,c));
          c = (pl_sInt) (*texpal++) - (pl_sInt) envpal[2]; *pal++ = plMax(0,plMin(255,c));
        }
      break;
      case PL_TEXENV_ENVMINUSTEX: // env-tex
        for (whichindex = 0; whichindex < m->Texture->NumColors; whichindex++) {
          c = -(pl_sInt) (*texpal++) - (pl_sInt) envpal[0]; *pal++ = plMax(0,plMin(255,c));
          c = -(pl_sInt) (*texpal++) - (pl_sInt) envpal[1]; *pal++ = plMax(0,plMin(255,c));
          c = -(pl_sInt) (*texpal++) - (pl_sInt) envpal[2]; *pal++ = plMax(0,plMin(255,c));
        }
      break;
      case PL_TEXENV_MIN:
        for (whichindex = 0; whichindex < m->Texture->NumColors; whichindex++) {
          *pal++ = plMin(texpal[0],envpal[0]);
          *pal++ = plMin(texpal[1],envpal[1]);
          *pal++ = plMin(texpal[2],envpal[2]);
          texpal+=3;
        }
      break;
      case PL_TEXENV_MAX:
      break;
        for (whichindex = 0; whichindex < m->Texture->NumColors; whichindex++) {
          *pal++ = plMax(texpal[0],envpal[0]);
          *pal++ = plMax(texpal[1],envpal[1]);
          *pal++ = plMax(texpal[2],envpal[2]);
          texpal+=3;
        }
      default: // add
        for (whichindex = 0; whichindex < m->Texture->NumColors; whichindex++) {
          c = (pl_sInt) (*texpal++) + (pl_sInt) envpal[0]; *pal++ = plMax(0,plMin(255,c));
          c = (pl_sInt) (*texpal++) + (pl_sInt) envpal[1]; *pal++ = plMax(0,plMin(255,c));
          c = (pl_sInt) (*texpal++) + (pl_sInt) envpal[2]; *pal++ = plMax(0,plMin(255,c));
        }
      break;
    }
    envpal += 3;
    m->_AddTable[whichlevel] = whichlevel*m->Texture->NumColors;
  }
}

static void _plGenerateTexturePalette(pl_Mat *m, pl_Texture *t) {
  pl_uChar *ppal, *pal;
  pl_sInt c, i, x;
  m->_ColorsUsed = t->NumColors;
  if (m->_RequestedColors) free(m->_RequestedColors);
  pal = m->_RequestedColors = (pl_uChar *) malloc(m->_ColorsUsed*3);
  ppal = t->PaletteData;
  i = t->NumColors;
  do {
    for (x = 0; x < 3; x ++) {
      c = m->Ambient[x] + *ppal++;
      *(pal++) = plMax(0,plMin(c,255));
    }
  } while (--i);
}

static void _plGeneratePhongTexturePalette(pl_Mat *m, pl_Texture *t) {
  double a, ca, da, cb;
  pl_uInt16 *addtable;
  pl_uChar *ppal, *pal;
  pl_sInt c, i, i2, x;
  pl_uInt num_shades;

  if (t->NumColors) num_shades = (m->NumGradients / t->NumColors);
  else num_shades=1;

  if (!num_shades) num_shades = 1;
  m->_ColorsUsed = num_shades*t->NumColors;
  if (m->_RequestedColors) free(m->_RequestedColors);
  pal = m->_RequestedColors = (pl_uChar *) malloc(m->_ColorsUsed*3);
  a = PL_PI/2.0;
  if (num_shades>1) da = (-PL_PI/2.0)/(num_shades-1);
  else da=0.0;
  i2 = num_shades;
  do {
    ppal = t->PaletteData;
    ca = cos((double) a);
    a += da;
    cb = pow(ca, (double) m->Shininess);
    i = t->NumColors;
    do {
      for (x = 0; x < 3; x ++) {
        c = (pl_sInt) ((cb*m->Specular[x])+(ca*m->Diffuse[x])+m->Ambient[x] + *ppal++);
        *(pal++) = plMax(0,plMin(c,255));
      }
    } while (--i);
  } while (--i2);
  ca = 0;
  if (m->_AddTable) free(m->_AddTable);
  m->_AddTable = (pl_uInt16 *) malloc(256*sizeof(pl_uInt16));
  addtable = m->_AddTable;
  i = 256;
  do {
    a = sin(ca) * num_shades;
    ca += PL_PI/512.0;
    *addtable++ = ((pl_sInt) a)*t->NumColors;
  } while (--i);
}

static void _plGeneratePhongTransparentPalette(pl_Mat *m) {
  m->_tsfact = (pl_sInt) (m->NumGradients*(1.0/(1+m->Transparent)));
  _plGeneratePhongPalette(m);
}

static void  _plGenerateTransparentPalette(pl_Mat *m) {
  m->_tsfact = 0;
  _plGeneratePhongPalette(m);
}

static void _plSetMaterialPutFace(pl_Mat *m) {
  m->_PutFace = 0;
  switch (m->_ft) {
    case PL_FILL_TRANSPARENT: switch(m->_st) {
      case PL_SHADE_NONE: case PL_SHADE_FLAT:
      case PL_SHADE_FLAT_DISTANCE: case PL_SHADE_FLAT_DISTANCE|PL_SHADE_FLAT:
        m->_PutFace = plPF_TransF;
      break;
      case PL_SHADE_GOURAUD: case PL_SHADE_GOURAUD_DISTANCE:
      case PL_SHADE_GOURAUD|PL_SHADE_GOURAUD_DISTANCE:
        m->_PutFace = plPF_TransG;
      break;
    }
    break;
    case PL_FILL_SOLID: switch(m->_st) {
      case PL_SHADE_NONE: case PL_SHADE_FLAT:
      case PL_SHADE_FLAT_DISTANCE: case PL_SHADE_FLAT_DISTANCE|PL_SHADE_FLAT:
        m->_PutFace = plPF_SolidF;
      break;
      case PL_SHADE_GOURAUD: case PL_SHADE_GOURAUD_DISTANCE:
      case PL_SHADE_GOURAUD|PL_SHADE_GOURAUD_DISTANCE:
        m->_PutFace = plPF_SolidG;
      break;
    }
    break;
    case PL_FILL_ENVIRONMENT:
    case PL_FILL_TEXTURE:
      if (m->PerspectiveCorrect) switch (m->_st) {
        case PL_SHADE_NONE: case PL_SHADE_FLAT:
        case PL_SHADE_FLAT_DISTANCE: case PL_SHADE_FLAT_DISTANCE|PL_SHADE_FLAT:
          m->_PutFace = plPF_PTexF;
        break;
        case PL_SHADE_GOURAUD: case PL_SHADE_GOURAUD_DISTANCE:
        case PL_SHADE_GOURAUD|PL_SHADE_GOURAUD_DISTANCE:
          m->_PutFace = plPF_PTexG;
        break;
      }
      else switch (m->_st) {
        case PL_SHADE_NONE: case PL_SHADE_FLAT:
        case PL_SHADE_FLAT_DISTANCE: case PL_SHADE_FLAT_DISTANCE|PL_SHADE_FLAT:
          m->_PutFace = plPF_TexF;
        break;
        case PL_SHADE_GOURAUD: case PL_SHADE_GOURAUD_DISTANCE:
        case PL_SHADE_GOURAUD|PL_SHADE_GOURAUD_DISTANCE:
          m->_PutFace = plPF_TexG;
        break;
      }
    break;
    case PL_FILL_TEXTURE|PL_FILL_ENVIRONMENT:
      m->_PutFace = plPF_TexEnv;
    break;
  }
}

typedef struct __ct {
  pl_uChar r,g,b;
  pl_Bool visited;
  struct __ct *next;
} _ct;

static int mdist(_ct *a, _ct *b) {
  return ((a->r-b->r)*(a->r-b->r)+(a->g-b->g)*(a->g-b->g)+(a->b-b->b)*(a->b-b->b));
}

void plMatMakeOptPal(pl_uChar *p, pl_sInt pstart,
                     pl_sInt pend, pl_Mat **materials, pl_sInt nmats) {
  pl_uChar *allColors = 0;
  pl_sInt numColors = 0, nc, x;
  pl_sInt len = pend + 1 - pstart;
  pl_sInt32 current, newnext, bestdist, thisdist;
  _ct *colorBlock, *best, *cp;

  for (x = 0; x < nmats; x ++) {
    if (materials[x]) {
      if (!materials[x]->_RequestedColors) plMatInit(materials[x]);
      if (materials[x]->_RequestedColors) numColors+=materials[x]->_ColorsUsed;
    }
  }
  if (!numColors) return;

  allColors=(pl_uChar*)malloc(numColors*3);
  numColors=0;

  for (x = 0; x < nmats; x ++) {
    if (materials[x]) {
      if (materials[x]->_RequestedColors)
        memcpy(allColors + (numColors*3), materials[x]->_RequestedColors,
             materials[x]->_ColorsUsed*3);
      numColors += materials[x]->_ColorsUsed;
    }
  }

  if (numColors <= len) {
    memcpy(p+pstart*3,allColors,numColors*3);
    free(allColors);
    return;
  }

  colorBlock = (_ct *) malloc(sizeof(_ct)*numColors);
  for (x = 0; x < numColors; x++) {
    colorBlock[x].r = allColors[x*3];
    colorBlock[x].g = allColors[x*3+1];
    colorBlock[x].b = allColors[x*3+2];
    colorBlock[x].visited = 0;
    colorBlock[x].next = 0;
  }
  free(allColors);

  /* Build a list, starting at color 0 */
  current = 0;
  nc = numColors;
  do {
    newnext = -1;
    bestdist = 300000000;
    colorBlock[current].visited = 1;
    for (x = 0; x < nc; x ++) {
      if (!colorBlock[x].visited) {
        thisdist = mdist(colorBlock + x, colorBlock + current);
        if (thisdist < 5) { colorBlock[x].visited = 1; numColors--; }
        else if (thisdist < bestdist) { bestdist = thisdist; newnext = x; }
      }
    }
    if (newnext != -1) {
      colorBlock[current].next = colorBlock + newnext;
      current = newnext;
    }
  } while (newnext != -1);
  colorBlock[current].next = 0; /* terminate the list */

  /* we now have a linked list starting at colorBlock, which is each one and
     it's closest neighbor */

  while (numColors > len) {
    bestdist = mdist(colorBlock,colorBlock->next);
    for (best = cp = colorBlock; cp->next; cp = cp->next) {
      if (bestdist > (thisdist = mdist(cp,cp->next))) {
        best = cp;
        bestdist = thisdist;
      }
    }
    best->r = ((int) best->r + (int) best->next->r)>>1;
    best->g = ((int) best->g + (int) best->next->g)>>1;
    best->b = ((int) best->b + (int) best->next->b)>>1;
    best->next = best->next->next;
    numColors--;
  }
  x = pstart*3;
  for (cp = colorBlock; cp; cp = cp->next) {
    p[x++] = cp->r;
    p[x++] = cp->g;
    p[x++] = cp->b;
  }
  free(colorBlock);
}