/* 3dflippo.c */

/*
 * 25/01/2006 c.e. prelz
 *
 * My second frei0r effect - more complex flipping
 *
 * Copyright (C) 2006 BEK - Bergen Senter for Elektronisk Kunst <bek@bek.no>
 * This file is a Frei0r plugin.
 *
 * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include "frei0r.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#define MSIZE 4
#define TWO_PI (M_PI*2.0)

enum axis
{
  AXIS_X,
  AXIS_Y,
  AXIS_Z
};

#include <assert.h>

typedef struct tdflippo_instance
{
  unsigned int width,height,fsize;
  int *mask;
  float flip[3],rate[3],center[2];
  unsigned char invertrot,dontblank,fillblack,mustrecompute;
} tdflippo_instance_t;

static float **newmat(unsigned char unit_flg);
static void matfree(float **mat);
static float **mat_translate(float tx,float ty,float tz);
static float **mat_rotate(enum axis ax,float angle);
static float **matmult(float **mat1,float **mat2);
static void vetmat(float **mat,float *x,float *y,float *z);
static void recompute_mask(tdflippo_instance_t* inst);

int f0r_init()
{
  return 1;
}

void f0r_deinit()
{
}

void f0r_get_plugin_info(f0r_plugin_info_t* flippoInfo)
{
  flippoInfo->name="3dflippo";
  flippoInfo->author="c.e. prelz AS FLUIDO <fluido@fluido.as>";
  flippoInfo->plugin_type=F0R_PLUGIN_TYPE_FILTER;
  flippoInfo->color_model=F0R_COLOR_MODEL_PACKED32;
  flippoInfo->frei0r_version=FREI0R_MAJOR_VERSION;
  flippoInfo->major_version=0;
  flippoInfo->minor_version=0;
  flippoInfo->num_params=11;
  flippoInfo->explanation="Frame rotation in 3d-space";
}

void f0r_get_param_info(f0r_param_info_t* info, int param_index)
{
  switch(param_index)
  {
  case 0:
    info->name="X axis rotation";
    info->type=F0R_PARAM_DOUBLE;
    info->explanation="Rotation on the X axis";
    break;
  case 1:
    info->name="Y axis rotation";
    info->type=F0R_PARAM_DOUBLE;
    info->explanation="Rotation on the Y axis";
    break;
  case 2:
    info->name="Z axis rotation";
    info->type=F0R_PARAM_DOUBLE;
    info->explanation="Rotation on the Z axis";
    break;
  case 3:
    info->name="X axis rotation rate";
    info->type=F0R_PARAM_DOUBLE;
    info->explanation="Rotation rate on the X axis";
    break;
  case 4:
    info->name="Y axis rotation rate";
    info->type=F0R_PARAM_DOUBLE;
    info->explanation="Rotation rate on the Y axis";
    break;
  case 5:
    info->name="Z axis rotation rate";
    info->type=F0R_PARAM_DOUBLE;
    info->explanation="Rotation rate on the Z axis";
    break;
  case 6:
    info->name="Center position (X)";
    info->type=F0R_PARAM_DOUBLE;
    info->explanation="Position of the center of rotation on the X axis";
    break;
  case 7:
    info->name="Center position (Y)";
    info->type=F0R_PARAM_DOUBLE;
    info->explanation="Position of the center of rotation on the Y axis";
    break;
  case 8:
    info->name="Invert rotation assignment";
    info->type=F0R_PARAM_BOOL;
    info->explanation="If true, when mapping rotation, make inverted (wrong) assignment";
    break;
  case 9:
    info->name="Don't blank mask";
    info->type=F0R_PARAM_BOOL;
    info->explanation="Mask for frame transposition is not blanked, so a trace of old transpositions is maintained";
    break;
  case 10:
    info->name="Fill with image or black";
    info->type=F0R_PARAM_BOOL;
    info->explanation="If true, pixels that are not transposed are black, otherwise, they are copied with the original";
    break;
  }
}

f0r_instance_t f0r_construct(unsigned int width,unsigned int height)
{
  tdflippo_instance_t *inst=
    (tdflippo_instance_t*)malloc(sizeof(tdflippo_instance_t));

  inst->width=width;
  inst->height=height;
  inst->fsize=width*height;

  inst->flip[0]=inst->flip[1]=inst->flip[2]=inst->rate[0]=inst->rate[1]=inst->rate[2]=0.5;
  
  inst->mask=malloc(sizeof(int)*inst->fsize);

  inst->invertrot=inst->dontblank=inst->fillblack=inst->mustrecompute=0;

  return (f0r_instance_t)inst;
}

void f0r_destruct(f0r_instance_t instance)
{
  tdflippo_instance_t* inst=(tdflippo_instance_t*)instance;

  free(inst->mask);
  free(inst);
}

void f0r_set_param_value(f0r_instance_t instance,
                         f0r_param_t param,int param_index)
{
  assert(instance);

  tdflippo_instance_t *inst=(tdflippo_instance_t*)instance;

  switch(param_index)
  {
  case 0:
    inst->flip[0]=(float)(*((double*)param));
    break;
  case 1:
    inst->flip[1]=(float)(*((double*)param));
    break;
  case 2:
    inst->flip[2]=(float)(*((double*)param));
    break;
  case 3:
    inst->rate[0]=(float)(*((double*)param));
    break;
  case 4:
    inst->rate[1]=(float)(*((double*)param));
    break;
  case 5:
    inst->rate[2]=(float)(*((double*)param));
    break;
  case 6:
    inst->center[0]=(float)(*((double*)param));
    break;
  case 7:
    inst->center[1]=(float)(*((double*)param));
    break;
  case 8:
    inst->invertrot=(*((double*)param)>=0.5);
    break;
  case 9:
    inst->dontblank=(*((double*)param)>=0.5);
    break;
  case 10:
    inst->fillblack=(*((double*)param)>=0.5);
    break;
  }

  if((param_index>=0 && param_index<=2) || (param_index>=6 && param_index<=9))
    inst->mustrecompute=1;
}

void f0r_get_param_value(f0r_instance_t instance,
                         f0r_param_t param,int param_index)
{
  assert(instance);

  tdflippo_instance_t *inst=(tdflippo_instance_t*)instance;

  switch(param_index)
  {
  case 0:
    *((double*)param)=inst->flip[0];
    break;
  case 1:
    *((double*)param)=inst->flip[1];
    break;
  case 2:
    *((double*)param)=inst->flip[2];
    break;
  case 3:
    *((double*)param)=inst->rate[0];
    break;
  case 4:
    *((double*)param)=inst->rate[1];
    break;
  case 5:
    *((double*)param)=inst->rate[2];
    break;
  case 6:
    *((double*)param)=inst->center[0];
    break;
  case 7:
    *((double*)param)=inst->center[1];
    break;
  case 8:
    *((double*)param)=(inst->invertrot ? 1.0 : 0.0);
    break;
  case 9:
    *((double*)param)=(inst->dontblank ? 1.0 : 0.0);
    break;
  case 10:
    *((double*)param)=(inst->fillblack ? 1.0 : 0.0);
    break;    
  }
}

void f0r_update(f0r_instance_t instance,double time,
                const uint32_t *inframe, uint32_t *outframe)
{
  assert(instance);

  tdflippo_instance_t* inst=(tdflippo_instance_t*)instance;  
  int i;

  if(inst->rate[0]!=0.5 || inst->rate[1]!=0.5 || inst->rate[2]!=0.5 || inst->mustrecompute)
  {
    inst->mustrecompute=0;
    
/*
 * We are changing: apply change and recompute mask
 */

    for(i=0;i<3;i++)
    {
      inst->flip[i]+=inst->rate[i]-0.5;
      if(inst->flip[i]<0.0)
	inst->flip[i]+=1.0;
      else if(inst->flip[i]>=1.0)
	inst->flip[i]-=1.0;
    }
    recompute_mask(inst);
  }

  for(i=0;i<inst->fsize;i++)
  {
    if(inst->mask[i]>=0)
      outframe[i]=inframe[inst->mask[i]];
    else if(!inst->fillblack)
      outframe[i]=inframe[i];
    else
      outframe[i]=0;
  }
}

static float **newmat(unsigned char unit_flg)
{
  int i;
  float **to_ret=malloc(sizeof(float *)*MSIZE);

  for(i=0;i<MSIZE;i++)
  {
    to_ret[i]=calloc(MSIZE,sizeof(float));
    if(unit_flg)
      to_ret[i][i]=1.0;
  }

  return to_ret;
}

static void matfree(float **mat)
{
  int i;

  for(i=0;i<MSIZE;i++)
    free(mat[i]);
  
  free(mat);
}

static float **mat_translate(float tx,float ty,float tz)
{
  float **mat=newmat(1);

  mat[0][3]=tx;
  mat[1][3]=ty;
  mat[2][3]=tz;

  return mat;
}

static float **mat_rotate(enum axis ax,float angle)
{
  float **mat=newmat(1);
  float sf=sinf(angle);
  float cf=cosf(angle);

  switch(ax)
  {
    case AXIS_X:
      mat[1][1]=cf;
      mat[1][2]=-sf;
      mat[2][1]=sf;
      mat[2][2]=cf;
      break;
    case AXIS_Y:
      mat[0][0]=cf;
      mat[0][2]=sf;
      mat[2][0]=-sf;
      mat[2][2]=cf;
      break;
    case AXIS_Z:
      mat[0][0]=cf;
      mat[0][1]=-sf;
      mat[1][0]=sf;
      mat[1][1]=cf;
      break;
  }
  return mat;
}  

static float **matmult(float **mat1,float **mat2)
{
  float **mat=newmat(0);
  int i,j,k;

  for(i=0;i<MSIZE;i++)
    for(j=0;j<MSIZE;j++)
      for(k=0;k<MSIZE;k++)
	mat[i][j]+=mat1[i][k]*mat2[k][j];
  
  matfree(mat1);
  matfree(mat2);

  return mat;
}

static void vetmat(float **mat,float *x,float *y,float *z)
{
  float v;
  float vet[]={*x,*y,*z,1.0};
  float *vetp[]={x,y,z,&v};
  int i,j;

  for(i=0;i<MSIZE;i++)
  {
    *(vetp[i])=0.0;

    for(j=0;j<MSIZE;j++)
      *(vetp[i])+=mat[i][j]*vet[j];
  }
}
  
static void recompute_mask(tdflippo_instance_t* inst)
{
  float xpos=(float)inst->width*inst->center[0];
  float ypos=(float)inst->height*inst->center[1];
  float **mat=mat_translate(xpos,ypos,0.0);
  
  if(inst->flip[0]!=0.5)
    mat=matmult(mat,mat_rotate(AXIS_X,(inst->flip[0]-0.5)*TWO_PI));
  if(inst->flip[1]!=0.5)
    mat=matmult(mat,mat_rotate(AXIS_Y,(inst->flip[1]-0.5)*TWO_PI));
  if(inst->flip[2]!=0.5)
    mat=matmult(mat,mat_rotate(AXIS_Z,(inst->flip[2]-0.5)*TWO_PI));
  
  mat=matmult(mat,mat_translate(-xpos,-ypos,0.0));
  
#if 0
  fprintf(stderr,"Resarra %.2f %.2f %.2f %.2f | %.2f %.2f %.2f %.2f | %.2f %.2f %.2f %.2f | %.2f %.2f %.2f %.2f\n",
	  mat[0][0],mat[0][1],mat[0][2],mat[0][3],
	  mat[1][0],mat[1][1],mat[1][2],mat[1][3],
	  mat[2][0],mat[2][1],mat[2][2],mat[2][3],
	  mat[3][0],mat[3][1],mat[3][2],mat[3][3]);
#endif
  
  int x,y,nx,ny,pos;
  float xf,yf,zf;

  if(!inst->dontblank)
    memset(inst->mask,0xff,sizeof(int)*inst->fsize);

  for(y=0,pos=0;y<inst->height;y++)
    for(x=0;x<inst->width;x++,pos++)
    {
      xf=x;
      yf=y;
      zf=0.0;
      vetmat(mat,&xf,&yf,&zf);
      nx=(int)(xf+0.5);
      ny=(int)(yf+0.5);
      
      if(nx>=0 && nx<inst->width && ny>=0 && ny<inst->height)
      {
	if(!inst->invertrot) 
	  inst->mask[ny*inst->width+nx]=pos;
	else
	  inst->mask[pos]=ny*inst->width+nx;
      }
    }
}
