/*
 *			GPAC - MPEG-4 Systems C Development Kit
 *
 *			Copyright (c) Jean Le Feuvre 2000-2005
 *					All rights reserved
 *
 *  This file is part of GPAC / 3D rendering plugin
 *
 *  GPAC 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, or (at your option)
 *  any later version.
 *   
 *  GPAC 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 GNU Make; see the file COPYING.  If not, write to
 *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. 
 *
 */



#include "render3d_nodes.h"

typedef struct
{
	SoundInterface snd_ifce;
	SFVec3f pos;
} Sound2DStack;

static void DestroySound2D(SFNode *node)
{
	Sound2DStack *st = (Sound2DStack *)Node_GetPrivate(node);
	free(st);
}

/*sound2D wraper - spacialization is not supported yet*/
static void RenderSound2D(SFNode *node, void *rs)
{
	RenderEffect3D *eff = (RenderEffect3D*) rs;
	M_Sound2D *snd = (M_Sound2D *)node;
	Sound2DStack *st = (Sound2DStack *)Node_GetPrivate(node);

	if (!snd->source) return;

	/*this implies no DEF/USE for real location...*/
	st->pos.x = snd->location.x;
	st->pos.y = snd->location.y;
	st->pos.z = 0;
	m4_mx_apply_vec(&eff->model_matrix, &st->pos);

	eff->sound_holder = &st->snd_ifce;
	Node_Render((SFNode *) snd->source, eff);
	eff->sound_holder = NULL;
	/*never cull Sound2d*/
	eff->trav_flags |= TF_DONT_CULL;
}
static Bool SND2D_GetChannelVolume(SFNode *node, Fixed *vol)
{
	Fixed volume = ((M_Sound2D *)node)->intensity;
	vol[0] = vol[1] = vol[2] = vol[3] = vol[4] = vol[5] = volume;
	return (volume==FIX_ONE) ? 0 : 1;
}

static u8 SND2D_GetPriority(SFNode *node)
{
	return 255;
}

void R3D_InitSound2D(Render3D *sr, SFNode *node)
{
	Sound2DStack *snd;
	SAFEALLOC(snd, sizeof(Sound2DStack));
	snd->snd_ifce.GetPriority = SND2D_GetPriority;
	snd->snd_ifce.GetChannelVolume = SND2D_GetChannelVolume;
	snd->snd_ifce.owner = node;
	Node_SetPrivate(node, snd);
	Node_SetRenderFunction(node, RenderSound2D);
	Node_SetPreDestroyFunction(node, DestroySound2D);
}


static Fixed snd_compute_gain(Fixed min_b, Fixed min_f, Fixed max_b, Fixed max_f, SFVec3f pos)
{	
	Fixed sqpos_x, sqpos_z;
	Fixed y_pos, x_pos, dist_ellip, viewp_dist, dist_from_foci_min, dist_from_foci_max, d_min, d_max, sqb_min, sqb_max;
	Fixed a_in = (min_f+min_b)/2;
	Fixed b_in = m4_sqrt(m4_mulfix(min_b, min_f));
	Fixed alpha_min = (min_f-min_b)/2;
	Fixed dist_foci_min = (min_f-min_b);
	Fixed a_out = (max_f+max_b)/2;	//first ellipse axis
	Fixed b_out = m4_sqrt(m4_mulfix(max_b, max_f));
	Fixed alpha_max = (max_f-max_b)/2; //origo from focus
	Fixed dist_foci_max = (max_f-max_b);
	Fixed x_min = 0;
	Fixed x_max = 0;
	Fixed y_min = 0;
	Fixed y_max = 0;
	Fixed k = (ABS(pos.z) >= FIX_EPSILON) ? m4_divfix(pos.x, pos.z) : 0;

	sqpos_x = m4_mulfix(pos.x, pos.x);
	sqpos_z = m4_mulfix(pos.z, pos.z);

	dist_from_foci_min = m4_sqrt(sqpos_z + sqpos_x) + m4_sqrt( m4_mulfix(pos.z - dist_foci_min, pos.z - dist_foci_min) + sqpos_x);
	dist_from_foci_max = m4_sqrt(sqpos_z + sqpos_x) + m4_sqrt( m4_mulfix(pos.z - dist_foci_max, pos.z - dist_foci_max) + sqpos_x);	
	d_min = min_f+min_b;
	d_max = max_f+max_b;
	if(dist_from_foci_max > d_max) return 0;
	else if (dist_from_foci_min <= d_min) return FIX_ONE;

	sqb_min = m4_mulfix(b_in, b_in);
	sqb_max = m4_mulfix(b_out, b_out);

	if (ABS(pos.z) > FIX_ONE/10000) {
		s32 sign = (pos.z>0) ? 1 : -1;
		Fixed a_in_k_sq, a_out_k_sq;
		a_in_k_sq = m4_mulfix(a_in, k);
		a_in_k_sq = m4_mulfix(a_in_k_sq, a_in_k_sq);

		x_min = m4_mulfix(alpha_min, sqb_min) + sign*m4_mulfix( m4_mulfix(a_in, b_in), m4_sqrt(a_in_k_sq + sqb_min - m4_mulfix( m4_mulfix(alpha_min, k), m4_mulfix(alpha_min, k))));
		x_min = m4_divfix(x_min, sqb_min + a_in_k_sq);
		y_min = m4_mulfix(k, x_min);

		a_out_k_sq = m4_mulfix(a_out, k);
		a_out_k_sq = m4_mulfix(a_out_k_sq, a_out_k_sq);

		x_max = m4_mulfix(alpha_max, sqb_max) + sign*m4_mulfix( m4_mulfix(a_out, b_out), m4_sqrt( a_out_k_sq + sqb_max - m4_mulfix( m4_mulfix(alpha_max, k), m4_mulfix(alpha_max, k))));
		x_max = m4_divfix(x_max, sqb_max + a_out_k_sq);
		y_max = m4_mulfix(k, x_max);
	} else {
		x_min = x_max = 0;
		y_min = m4_mulfix(b_in, m4_sqrt(FIX_ONE - m4_mulfix( m4_divfix(alpha_min,a_in), m4_divfix(alpha_min,a_in)) ) );
		y_max = m4_mulfix(b_out, m4_sqrt(FIX_ONE - m4_mulfix( m4_divfix(alpha_max,a_out), m4_divfix(alpha_max,a_out)) ) );
	}

	y_pos = m4_sqrt(sqpos_x) - y_min;
	x_pos = pos.z - x_min;
	x_max -= x_min;
	y_max -= y_min;
	dist_ellip = m4_sqrt( m4_mulfix(y_max, y_max) + m4_mulfix(x_max, x_max));
	viewp_dist = m4_sqrt( m4_mulfix(y_pos, y_pos) + m4_mulfix(x_pos, x_pos));
	viewp_dist = m4_divfix(viewp_dist, dist_ellip);

	return FLT2FIX ( (Float) pow(10.0,- FIX2FLT(viewp_dist)));
}


typedef struct
{
	SoundInterface snd_ifce;
	M4Matrix mx;
	SFVec3f last_pos;
	Bool identity;
	/*local system*/
	Fixed intensity;
	Fixed lgain, rgain;
} SoundStack;

static void DestroySound(SFNode *node)
{
	SoundStack *st = (SoundStack *)Node_GetPrivate(node);
	free(st);
}

static void RenderSound(SFNode *node, void *rs)
{
	RenderEffect3D *eff = (RenderEffect3D*) rs;
	M_Sound *snd = (M_Sound *)node;
	SoundStack *st = (SoundStack *)Node_GetPrivate(node);

	if (!snd->source) return;

	eff->sound_holder = &st->snd_ifce;

	/*forward in case we're switched off*/
	if (eff->trav_flags & TF_SWITCHED_OFF) {
		Node_Render((SFNode *) snd->source, eff);
	}
	else if (eff->traversing_mode==TRAVERSE_GET_BOUNDS) {
		/*we can't cull sound since*/
		eff->trav_flags |= TF_DONT_CULL;
	} else if (eff->traversing_mode==TRAVERSE_SORT) {
		M4Matrix mx;
		SFVec3f usr, snd_dir, pos;
		Fixed mag, ang;
		/*this implies no DEF/USE for real location...*/
		m4_mx_copy(st->mx, eff->model_matrix);
		m4_mx_copy(mx, eff->model_matrix);
		m4_mx_inverse(&mx);

		snd_dir = snd->direction;
		m4_vec_norm(&snd_dir);

		/*get user location*/
		usr = eff->camera->position;
		m4_mx_apply_vec(&mx, &usr);

		/*recenter to ellipse focal*/
		m4_vec_diff(usr, usr, snd->location);
		mag = m4_vec_len(usr);
		if (!mag) mag = FIX_ONE/10;
		ang = m4_divfix(m4_vec_dot(snd_dir, usr), mag);
	
		usr.z = m4_mulfix(ang, mag);
		usr.x = m4_sqrt(m4_mulfix(mag, mag) - m4_mulfix(usr.z, usr.z));
		usr.y = 0;
		if (!m4_vec_equal(usr, st->last_pos)) {
			st->intensity = snd_compute_gain(snd->minBack, snd->minFront, snd->maxBack, snd->maxFront, usr);
			st->intensity *= snd->intensity;
			st->last_pos = usr;
		}
		st->identity = (st->intensity==FIX_ONE) ? 1 : 0;

		if (snd->spatialize) {
			Fixed ang, sign;
			SFVec3f cross;
			pos = snd->location;
			m4_mx_apply_vec(&eff->model_matrix, &pos);
			m4_vec_diff(pos, pos, eff->camera->position);
			m4_vec_diff(usr, eff->camera->target, eff->camera->position);
			m4_vec_norm(&pos);
			m4_vec_norm(&usr);

			ang = m4_acos(m4_vec_dot(usr, pos));
			/*get orientation*/
			cross = m4_vec_cross(usr, pos);
			sign = m4_vec_dot(cross, eff->camera->up);
			if (sign>0) ang *= -1;
			ang = (FIX_ONE + m4_sin(ang)) / 2;
			st->lgain = (FIX_ONE - m4_mulfix(ang, ang));
			st->rgain = FIX_ONE - m4_mulfix(FIX_ONE - ang, FIX_ONE - ang);
			/*renorm between 0 and 1*/
			st->lgain = m4_mulfix(st->lgain, 4*st->intensity/3);
			st->rgain = m4_mulfix(st->rgain, 4*st->intensity/3);

			if (st->identity && ((st->lgain!=FIX_ONE) || (st->rgain!=FIX_ONE))) st->identity = 0;
		} else {
			st->lgain = st->rgain = FIX_ONE;
		}
		Node_Render((SFNode *) snd->source, eff);
	}

	eff->sound_holder = NULL;
}
static Bool SND_GetChannelVolume(SFNode *node, Fixed *vol)
{
	M_Sound *snd = (M_Sound *)node;
	SoundStack *st = (SoundStack *)Node_GetPrivate(node);

	vol[2] = vol[3] = vol[4] = vol[5] = st->intensity;
	if (snd->spatialize) {
		vol[0] = st->lgain;
		vol[1] = st->rgain;
	} else {
		vol[0] = vol[1] = st->intensity;
	}
	return !st->identity;
}
static u8 SND_GetPriority(SFNode *node)
{
	return (u8) ((M_Sound *)node)->priority*255;
}

void R3D_InitSound(Render3D *sr, SFNode *node)
{
	SoundStack *snd;
	SAFEALLOC(snd, sizeof(SoundStack));
	snd->snd_ifce.GetChannelVolume = SND_GetChannelVolume;
	snd->snd_ifce.GetPriority = SND_GetPriority;
	snd->snd_ifce.owner = node;
	Node_SetPrivate(node, snd);
	Node_SetRenderFunction(node, RenderSound);
	Node_SetPreDestroyFunction(node, DestroySound);
}

