/*
Copyright (C) 1999-2006 Id Software, Inc. and contributors.
For a list of contributors, see the accompanying CONTRIBUTORS file.

This file is part of GtkRadiant.

GtkRadiant 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.

GtkRadiant 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 GtkRadiant; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/


#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <memory.h>
#include "animcomp.h"


void	*SafeMalloc(size_t n, char *desc);



float *matrix;
float *delta;
float *best;
float *comp;
float *tcomp;
float *bestcomp;
float *frames;
float *base;

int MatWidth;
int MatHeight;
int CFrameSize;
int nFrames;


void AnimCompressInit(int nframes,int nVerts,int CompressedFrameSize)
{
	nFrames=nframes;
	MatWidth=nVerts*3;
	MatHeight=CompressedFrameSize;
	CFrameSize=CompressedFrameSize;
	matrix=(float *)SafeMalloc(MatWidth*MatHeight*sizeof(float), "AnimCompressInit");
	best=(float *)SafeMalloc(MatWidth*MatHeight*sizeof(float), "AnimCompressInit");
	delta=(float *)SafeMalloc(MatWidth*MatHeight*sizeof(float), "AnimCompressInit");
	comp=(float *)SafeMalloc(CFrameSize*nFrames*sizeof(float), "AnimCompressInit");
	tcomp=(float *)SafeMalloc(CFrameSize*nFrames*sizeof(float), "AnimCompressInit");
	bestcomp=(float *)SafeMalloc(CFrameSize*nFrames*sizeof(float), "AnimCompressInit");
	base=(float *)SafeMalloc(MatWidth*sizeof(float), "AnimCompressInit");
	frames=(float *)SafeMalloc(MatWidth*nFrames*sizeof(float), "AnimCompressInit");
}

void AnimSetFrame(int frame,int index,float x,float y,float z)
{
	frames[frame*MatWidth+index*3]=x;
	frames[frame*MatWidth+index*3+1]=y;
	frames[frame*MatWidth+index*3+2]=z;
}

typedef struct 
{
	int index;
	float val;
} SORTP;


#define F_RANDOM (((float)rand())/(float)RAND_MAX)

extern void DOsvd(float *a,float *res,float *comp,float *values,int nframes,int framesize,int compressedsize);

void AnimCompressDoit()
{
	float compression;
	float *rescale;
	float *ans;
	float maxdev;
	float avedev;
	float tmp;
	int j,k,l,numave;

	for (k=0;k<MatWidth;k++)
		base[k]=0.0f;
	for (j=0;j<nFrames;j++)
		for (k=0;k<MatWidth;k++)
			base[k]+=frames[j*MatWidth+k];
	tmp=1.0f/(float)nFrames;
	for (k=0;k<MatWidth;k++)
		base[k]*=tmp;
	for (j=0;j<nFrames;j++)
		for (k=0;k<MatWidth;k++)
			frames[j*MatWidth+k]-=base[k];

	ans=(float *)SafeMalloc(sizeof(float)*MatWidth, "AnimCompressDoit");
	rescale=(float *)SafeMalloc(sizeof(float)*CFrameSize, "AnimCompressDoit");
	DOsvd(frames,best,bestcomp,rescale,nFrames,MatWidth,MatHeight);
	avedev=0.0;
	for (l=0;l<CFrameSize;l++)
		avedev+=rescale[l];
	for (l=0;l<CFrameSize;l++)
		printf("%3.1f ",100.0f*rescale[l]/avedev);
	printf("\n");
	for (j=0;j<nFrames;j++)
	{
		for (l=0;l<CFrameSize;l++)
		{
			bestcomp[j*CFrameSize+l]=0.0;
			for (k=0;k<MatWidth;k++)
				bestcomp[j*CFrameSize+l]+=best[l*MatWidth+k]*frames[j*MatWidth+k];
		}
	}
	numave=0;
	avedev=0.0;
	maxdev=0.0;
	for (j=0;j<nFrames;j++)
	{
		for (k=0;k<MatWidth;k++)
		{
			ans[k]=0.0;
			for (l=0;l<CFrameSize;l++)
				ans[k]+=best[l*MatWidth+k]*bestcomp[j*CFrameSize+l];
			ans[k]-=frames[j*MatWidth+k];
			tmp=(float)fabs(ans[k]);
			if (tmp>maxdev)
				maxdev=tmp;
			avedev+=tmp;
			numave++;
		}
	}
	avedev/=(float)numave;
printf("%f Max Deviation (inches)  %f Ave Dev. (inches)\n",maxdev,avedev);
printf("%d bytes original size\n",MatWidth*nFrames);
printf("%d bytes of overhead\n",MatWidth*MatHeight);
printf("%d bytes/frame * %d frames = %d bytes\n",CFrameSize,nFrames,CFrameSize*nFrames);
	compression=(float)(MatWidth*MatHeight+CFrameSize*nFrames+MatWidth);
	compression/=(float)(MatWidth*nFrames);
printf("Overall compression = %f %%\n",100.0f-100.0f*compression);
	compression=(float)(CFrameSize);
	compression/=(float)(MatWidth);
printf("frame size compression = %f %%\n",100.0f-100.0f*compression);
	free(rescale);
	free(ans);
}

void AnimCompressToBytes(float *trans,float *scale,char *mat,char *ccomp,unsigned char *cbase,float *cscale,float *coffset,float *bmin,float *bmax)
{
	int k,l,nv,j;
	float maxdev;
	float avedev;
	float tmp;
	int numave;
	float t,mx;
	float *ans;


	nv=MatWidth/3;

	trans[0]=1E30f;
	scale[0]=-1E30f;
	trans[1]=1E30f;
	scale[1]=-1E30f;
	trans[2]=1E30f;
	scale[2]=-1E30f;
	for (k=0;k<MatWidth;k+=3)
	{
		if (base[k]>scale[0])
			scale[0]=base[k];
		if (base[k]<trans[0])
			trans[0]=base[k];

		if (base[k+1]>scale[1])
			scale[1]=base[k+1];
		if (base[k+1]<trans[1])
			trans[1]=base[k+1];

		if (base[k+2]>scale[2])
			scale[2]=base[k+2];
		if (base[k+2]<trans[2])
			trans[2]=base[k+2];
	}

	scale[0]-=trans[0];
	scale[1]-=trans[1];
	scale[2]-=trans[2];
	scale[0]/=255.0f;
	scale[1]/=255.0f;
	scale[2]/=255.0f;
	for (k=0;k<MatWidth;k+=3)
	{
		t=(base[k]-trans[0])/scale[0];
		if (t<0.0f)
			t=0.0f;
		if (t>255.0f)
			t=255.0f;
		cbase[k]=(unsigned char)t;

		t=(base[k+1]-trans[1])/scale[1];
		if (t<0.0f)
			t=0.0f;
		if (t>255.0f)
			t=255.0f;
		cbase[k+1]=(unsigned char)t;

		t=(base[k+2]-trans[2])/scale[2];
		if (t<0.0f)
			t=0.0f;
		if (t>255.0f)
			t=255.0f;
		cbase[k+2]=(unsigned char)t;
	}
	for (l=0;l<MatHeight;l++)
	{
		mx=0.0;
		for (k=0;k<MatWidth;k++)
		{
			if (fabs(best[l*MatWidth+k])>mx)
				mx=(float)fabs(best[l*MatWidth+k]);
		}
		if (mx>1E-8)
		{
			mx/=127.0f;
			coffset[l]=1E30f;
			cscale[l]=-1E30f;
			for (j=0;j<nFrames;j++)
			{
				bestcomp[j*MatHeight+l]*=mx;
				if (bestcomp[j*MatHeight+l]>cscale[l])
					cscale[l]=bestcomp[j*MatHeight+l];
				if (bestcomp[j*MatHeight+l]<coffset[l])
					coffset[l]=bestcomp[j*MatHeight+l];
			}
			cscale[l]-=coffset[l];
			if (cscale[l]>1E-10)
			{
				for (j=0;j<nFrames;j++)
				{
					tmp=254.0f*(bestcomp[j*MatHeight+l]-coffset[l])/cscale[l]-127.0f;
					if (tmp>127.0f)
						tmp=127.0f;
					if (tmp<-127.0f)
						tmp=-127.0f;
					ccomp[j*MatHeight+l]=(char)floor(tmp+0.5);
				}
				coffset[l]+=cscale[l]*127.0f/254.0f;
				cscale[l]/=254.0f;
			}
			else
			{
				cscale[l]=1.0f;
				coffset[l]=0.0f;
				for (j=0;j<nFrames;j++)
					ccomp[j*MatHeight+l]=0;
			}
			mx=1.0f/mx;
			for (k=0;k<MatWidth;k++)
			{
				tmp=best[l*MatWidth+k]*mx;
				if (tmp>127.0f)
					tmp=127.0f;
				if (tmp<-127.0f)
					tmp=-127.0f;
				mat[k*MatHeight+l]=(char)floor(tmp+0.5);
			}
		}
		else
		{
			cscale[l]=1.0f;
			coffset[l]=0.0f;
			for (j=0;j<nFrames;j++)
				ccomp[j*MatHeight+l]=0;
			for (k=0;k<MatWidth;k++)
				mat[k*MatHeight+l]=0;
		}
	}
	bmin[0]=1E30f;
	bmin[1]=1E30f;
	bmin[2]=1E30f;
	bmax[0]=-1E30f;
	bmax[1]=-1E30f;
	bmax[2]=-1E30f;
	numave=0;
	avedev=0.0;
	maxdev=0.0;
	ans=(float *)SafeMalloc(sizeof(float)*MatWidth, "AnimCompressToBytes");
	for (j=0;j<nFrames;j++)
	{
		for (k=0;k<MatWidth;k++)
		{
			ans[k]=0.0;
			for (l=0;l<CFrameSize;l++)
				ans[k]+=(float)(mat[l+k*MatHeight])*((float)(ccomp[j*CFrameSize+l])*cscale[l]+coffset[l]);
			ans[k]+=(float)(cbase[k])*scale[k%3]+trans[k%3];
			tmp=(float)fabs(ans[k]-frames[j*MatWidth+k]-base[k]);
			if (tmp>maxdev)
				maxdev=tmp;
			avedev+=tmp;
			numave++;

			if (bmin[k%3]>ans[k])
				bmin[k%3]=ans[k];
			if (bmax[k%3]<ans[k])
				bmax[k%3]=ans[k];
		}
	}
	avedev/=(float)numave;
printf("%f Max Deviation (inches)  %f Ave Dev. (inches)\n",maxdev,avedev);
	free(ans);
}

void AnimCompressGetMatrix(float *mat)
{
	int k,l;
	for (k=0;k<MatWidth;k++)
		for (l=0;l<MatHeight;l++)
			mat[k*MatHeight+l]=best[l*MatWidth+k];
}

void AnimCompressGetFrames(float *mat)
{
	memcpy(mat,bestcomp,CFrameSize*nFrames*sizeof(float));
}

void AnimCompressGetBase(int i,float *x,float *y,float *z)
{
	*x=base[i*3];
	*y=base[i*3+1];
	*z=base[i*3+2];
}

void AnimCompressEnd()
{
	free(matrix);
	free(best);
	free(delta);
	free(comp);
	free(tcomp);
	free(bestcomp);
	free(base);
	free(frames);
}
