/*
 *			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"


M4Err R3D_GetViewpoint(VisualRenderer *vr, u32 viewpoint_idx, const char **outName, Bool *is_bound)
{
	u32 count;
	SFNode *n;
	Render3D *sr = (Render3D *) vr->user_priv;
	if (!sr->surface) return M4BadParam;
	count = ChainGetCount(sr->surface->view_stack);
	if (!viewpoint_idx) return M4BadParam;
	if (viewpoint_idx>count) return M4EOF;

	n = ChainGetEntry(sr->surface->view_stack, viewpoint_idx-1);
	switch (Node_GetTag(n)) {
	case TAG_MPEG4_Viewport: *outName = ((M_Viewport*)n)->description.buffer; *is_bound = ((M_Viewport*)n)->isBound;return M4OK;
	case TAG_MPEG4_Viewpoint: case TAG_X3D_Viewpoint: *outName = ((M_Viewpoint*)n)->description.buffer; *is_bound = ((M_Viewpoint*)n)->isBound; return M4OK;
	default: *outName = NULL; return M4OK;
	}
}

M4Err R3D_SetViewpoint(VisualRenderer *vr, u32 viewpoint_idx, const char *viewpoint_name)
{
	u32 count, i;
	SFNode *n;
	Render3D *sr = (Render3D *) vr->user_priv;
	if (!sr->surface) return M4BadParam;
	count = ChainGetCount(sr->surface->view_stack);
	if (viewpoint_idx>count) return M4BadParam;
	if (!viewpoint_idx && !viewpoint_name) return M4BadParam;

	if (viewpoint_idx) {
		Bool bind;
		n = ChainGetEntry(sr->surface->view_stack, viewpoint_idx-1);
		bind = Bindable_GetIsBound(n);
		Bindable_SetSetBind(n, !bind);
		return M4OK;
	}
	for (i=0; i<count;i++) {
		char *name = NULL;
		n = ChainGetEntry(sr->surface->view_stack, viewpoint_idx-1);
		switch (Node_GetTag(n)) {
		case TAG_MPEG4_Viewport: name = ((M_Viewport*)n)->description.buffer; break;
		case TAG_MPEG4_Viewpoint: case TAG_X3D_Viewpoint: name = ((M_Viewpoint*)n)->description.buffer; break;
		default: break;
		}
		if (name && !stricmp(name, viewpoint_name)) {
			Bool bind = Bindable_GetIsBound(n);
			Bindable_SetSetBind(n, !bind);
			return M4OK;
		}
	}
	return M4BadParam;
}


#define VPCHANGED(__comp) { M4Event evt; evt.type = M4E_VIEWPOINTS; M4USER_SENDEVENT(__comp->user, &evt); }



static void DestroyViewStack(SFNode *node)
{
	ViewStack *st = (ViewStack *) Node_GetPrivate(node);
	PreDestroyBindable(node, st->reg_stacks);
	DeleteChain(st->reg_stacks);
	VPCHANGED(st->compositor);
	free(st);
}

static void viewport_set_bind(SFNode *node)
{
	ViewStack *st = (ViewStack *) Node_GetPrivate(node);
	Bindable_OnSetBind(node, st->reg_stacks);
	SR_Invalidate(st->compositor, NULL);
	/*notify change of vp stack*/
	VPCHANGED(st->compositor);
	/*and dirty ourselves to force frustrum update*/
	Node_SetDirty(node, 0);
}

static void RenderViewport(SFNode *node, void *rs)
{
	Fixed ar, sx, sy, w, h, tx, ty;
	M4Matrix2D mat;
	M4Matrix mx;
	M4Rect rc, rc_bckup;
	Bool is_layer;
	RenderEffect3D *eff = (RenderEffect3D *)rs;
	M_Viewport *vp = (M_Viewport*) node;
	ViewStack *st = (ViewStack *) Node_GetPrivate(node);

	assert(eff->viewpoints);
	if (eff->camera->is_3D) return;

	/*first traverse, bound if needed*/
	if (ChainFindEntry(eff->viewpoints, node) < 0) {
		ChainAddEntry(eff->viewpoints, node);
		assert(ChainFindEntry(st->reg_stacks, eff->viewpoints)==-1);
		ChainAddEntry(st->reg_stacks, eff->viewpoints);

		if (ChainGetEntry(eff->viewpoints, 0) == vp) {
			if (!vp->isBound) Bindable_SetIsBound(node, 1);
		} else {
			if (IS_IsDefaultView(node)) Bindable_SetSetBind(node, 1);
		}
		VPCHANGED(st->compositor);
		/*in any case don't draw the first time (since the viewport could have been declared last)*/
		SR_Invalidate(st->compositor, NULL);
		return;
	}
	/*not bound ignore*/
	if (!vp->isBound) return;
	if (eff->traversing_mode != TRAVERSE_RENDER_BINDABLE) return;
	
	is_layer = (eff->viewpoints == eff->surface->view_stack) ? 0 : 1;
	/*if no parent this is the main viewport, don't update if not changed*/
	if (!is_layer && !Node_GetDirty(node)) return;

	Node_ClearDirty(node);

	m4_mx2d_init(mat);
	m4_mx2d_add_translation(&mat, vp->position.x, vp->position.y);
	m4_mx2d_add_rotation(&mat, 0, 0, vp->orientation);

	//compute scaling ratio
	m4_rect_center(&rc, vp->size.x, vp->size.y);
	m4_mx2d_apply_rect(&mat, &rc);

	w = eff->bbox.max_edge.x - eff->bbox.min_edge.x;
	h = eff->bbox.max_edge.y - eff->bbox.min_edge.y;
	ar = m4_divfix(h, w);

	rc_bckup = rc;

	switch (vp->fit) {
	/*covers all area and respect aspect ratio*/
	case 2:
		if (m4_divfix(rc.width, w) > m4_divfix(rc.height, h)) {
			rc.width = m4_muldiv(rc.width, h, rc.height);
			rc.height = h;
		} else {
			rc.height = m4_muldiv(rc.height , w, rc.width);
			rc.width = w;
		}
		break;
	/*fits inside the area and respect AR*/
	case 1:
		if (m4_divfix(rc.width, w)> m4_divfix(rc.height, h)) {
			rc.height = m4_muldiv(rc.height, w, rc.width);
			rc.width = w;
		} else {
			rc.width = m4_muldiv(rc.width, h, rc.height);
			rc.height = h;
		}
		break;
	/*fit entirely: nothing to change*/
	case 0:
		rc.width = w;
		rc.height = h;
		break;
	default:
		return;
	}
	sx = m4_divfix(rc_bckup.width , rc.width);
	sy = m4_divfix(rc_bckup.height , rc.height);

	rc.x = - rc.width/2;
	rc.y = rc.height/2;

	tx = ty = 0;
	if (vp->fit) {
		/*left alignment*/
		if (vp->alignment.vals[0] == -1) tx = rc.width/2 - w/2;
		else if (vp->alignment.vals[0] == 1) tx = w/2 - rc.width/2;

		/*top-alignment*/
		if (vp->alignment.vals[1]==-1) ty = rc.height/2 - h/2;
		else if (vp->alignment.vals[1]==1) ty = h/2 - rc.height/2;
	}

	
	m4_mx_from_mx2d(&mx, &mat);
	m4_mx_add_scale(&mx, sx, sy, FIX_ONE);
	m4_mx_add_translation(&mx, -tx, -ty, 0);
	m4_mx_inverse(&mx);

	/*in layers directly modify the model matrix*/
	if (eff->viewpoints != eff->surface->view_stack) {
		m4_mx_add_matrix(&eff->model_matrix, &mx);
	} else {
		m4_mx_copy(eff->camera->viewport, mx);
		eff->camera->flags = (CAM_HAS_VIEWPORT | CAM_IS_DIRTY);
	}
}

void R3D_InitViewport(Render3D *sr, SFNode *node)
{
	ViewStack *st = malloc(sizeof(ViewStack));
	memset(st, 0, sizeof(ViewStack));
	st->reg_stacks = NewChain();
	traversable_setup(st, node, sr->compositor);
	Node_SetPrivate(node, st);
	Node_SetRenderFunction(node, RenderViewport);
	Node_SetPreDestroyFunction(node, DestroyViewStack);
	((M_Viewport*)node)->on_set_bind = viewport_set_bind;
}

static void viewpoint_set_bind(SFNode *node)
{
	ViewStack *st = (ViewStack *) Node_GetPrivate(node);
	Bindable_OnSetBind(node, st->reg_stacks);
	SR_Invalidate(st->compositor, NULL);
	/*notify change of vp stack*/
	VPCHANGED(st->compositor);
	/*and dirty ourselves to force frustrum update*/
	Node_SetDirty(node, 0);

	if (!((M_Viewpoint*)node)->isBound) 
		st->prev_was_bound = 0;
}

static void RenderViewpoint(SFNode *node, void *rs)
{
	SFVec3f pos, v1, v2;
	SFRotation ori;
	M4Matrix mx;
	RenderEffect3D *eff = (RenderEffect3D *)rs;
	M_Viewpoint *vp = (M_Viewpoint*) node;
	ViewStack *st = (ViewStack *) Node_GetPrivate(node);

	assert(eff->viewpoints);
	if (!eff->camera->is_3D) return;

	/*first traverse, bound if needed*/
	if (ChainFindEntry(eff->viewpoints, node) < 0) {
		ChainAddEntry(eff->viewpoints, node);
		assert(ChainFindEntry(st->reg_stacks, eff->viewpoints)==-1);
		ChainAddEntry(st->reg_stacks, eff->viewpoints);

		if (ChainGetEntry(eff->viewpoints, 0) == vp) {
			if (!vp->isBound) Bindable_SetIsBound(node, 1);
		} else {
			if (IS_IsDefaultView(node)) Bindable_SetSetBind(node, 1);
		}
		VPCHANGED(st->compositor);
		/*in any case don't draw the first time (since the viewport could have been declared last)*/
		SR_Invalidate(st->compositor, NULL);
	}
	/*not rendering vp, return*/
	if (eff->traversing_mode != TRAVERSE_RENDER_BINDABLE) {
		/*store model matrix if changed - NOTE: we always have a 1-frame delay between VP used and real world...
		we could remove this by pre-rendering the scene before applying vp, but that would mean 2 scene 
		traversals*/
		if (eff->traversing_mode==TRAVERSE_SORT) {
			if (!m4_mx_equal(&st->world_view_mx, &eff->model_matrix)) {
				m4_mx_copy(st->world_view_mx, eff->model_matrix);
				Node_SetDirty(node, 0);
			}
		}
		eff->trav_flags |= TF_DONT_CULL;
		return;
	}

	/*not bound or in 2D surface*/
	if (!vp->isBound || !eff->navigations) return;

	if (!Node_GetDirty(node)) return;
	Node_ClearDirty(node);

	/*move to local system*/
	m4_mx_copy(mx, st->world_view_mx);
	m4_mx_add_translation(&mx, vp->position.x, vp->position.y, vp->position.z);
	m4_mx_add_rotation(&mx, vp->orientation.q, vp->orientation.x, vp->orientation.y, vp->orientation.z);
	m4_mx_decompose(&mx, &pos, &v1, &ori, &v2);
	/*get center*/
	v1.x = v1.y = v1.z = 0;
	/*X3D specifies examine center*/
	if (Node_GetTag(node)==TAG_X3D_Viewpoint) v1 = ((X_Viewpoint *)node)->centerOfRotation;
	m4_mx_apply_vec(&st->world_view_mx, &v1);
	/*set frustrum param - animate only if not bound last frame and jump false*/
	VS_ViewpointChange(eff, node, (!st->prev_was_bound && !vp->jump) ? 1 : 0, vp->fieldOfView, pos, ori, v1);
	st->prev_was_bound = 1;
}

void R3D_InitViewpoint(Render3D *sr, SFNode *node)
{
	ViewStack *st = malloc(sizeof(ViewStack));
	memset(st, 0, sizeof(ViewStack));
	st->reg_stacks = NewChain();
	m4_mx_init(st->world_view_mx);
	traversable_setup(st, node, sr->compositor);
	Node_SetPrivate(node, st);
	Node_SetRenderFunction(node, RenderViewpoint);
	Node_SetPreDestroyFunction(node, DestroyViewStack);
	((M_Viewpoint*)node)->on_set_bind = viewpoint_set_bind;
}


static void navinfo_set_bind(SFNode *node)
{
	ViewStack *st = (ViewStack *) Node_GetPrivate(node);
	Bindable_OnSetBind(node, st->reg_stacks);
	SR_Invalidate(st->compositor, NULL);
}

static void RenderNavigationInfo(SFNode *node, void *rs)
{
	u32 i;
	RenderEffect3D *eff = (RenderEffect3D *)rs;
	M_NavigationInfo *ni = (M_NavigationInfo *) node;
	ViewStack *st = (ViewStack *) Node_GetPrivate(node);

	if (!eff->navigations) return;

	/*first traverse, bound if needed*/
	if (ChainFindEntry(eff->navigations, node) < 0) {
		ChainAddEntry(eff->navigations, node);
		if (ChainGetEntry(eff->navigations, 0) == ni) {
			if (!ni->isBound) Bindable_SetIsBound(node, 1);
		}
		assert(ChainFindEntry(st->reg_stacks, eff->navigations)==-1);
		ChainAddEntry(st->reg_stacks, eff->navigations);
		/*in any case don't draw the first time*/
		SR_Invalidate(st->compositor, NULL);
		return;
	}
	/*not bound*/
	if (!ni->isBound) return;
	/*not rendering, return*/
	if (eff->traversing_mode != TRAVERSE_RENDER_BINDABLE) return;

	if (!Node_GetDirty(node)) return;
	Node_ClearDirty(node);

	eff->camera->navigation_flags = 0;
	eff->camera->navigate_mode = 0;
	for (i=0; i<ni->type.count; i++) {
		if (ni->type.vals[i] && !stricmp(ni->type.vals[i], "ANY")) eff->camera->navigation_flags |= NAV_ANY;
		else if (ni->type.vals[i] && !stricmp(ni->type.vals[i], "NONE")) eff->camera->navigate_mode = M4_NavNone;
		else if (ni->type.vals[i] && !stricmp(ni->type.vals[i], "WALK")) eff->camera->navigate_mode = M4_NavWalk;
		else if (ni->type.vals[i] && !stricmp(ni->type.vals[i], "EXAMINE")) eff->camera->navigate_mode = M4_NavExamine;
		else if (ni->type.vals[i] && !stricmp(ni->type.vals[i], "FLY")) eff->camera->navigate_mode = M4_NavFly;
		else if (ni->type.vals[i] && !stricmp(ni->type.vals[i], "QTVR")) eff->camera->navigate_mode = M4_NavVR;
	}
	if (ni->headlight) eff->camera->navigation_flags |= NAV_HEADLIGHT;

	eff->camera->speed = ni->speed;
    eff->camera->visibility = ni->visibilityLimit;
	if (ni->avatarSize.count) eff->camera->avatar_size.x = ni->avatarSize.vals[0];
	if (ni->avatarSize.count>1) eff->camera->avatar_size.y = ni->avatarSize.vals[1];
	if (ni->avatarSize.count>2) eff->camera->avatar_size.z = ni->avatarSize.vals[2];

	if (eff->is_pixel_metrics) {
		u32 s = MAX(eff->surface->width, eff->surface->height);
		s /= 2;
//		eff->camera->speed = ni->speed;
	    eff->camera->visibility *= s;
		eff->camera->avatar_size.x *= s;
		eff->camera->avatar_size.y *= s;
		eff->camera->avatar_size.z *= s;
	}
}

void R3D_InitNavigationInfo(Render3D *sr, SFNode *node)
{
	ViewStack *st = malloc(sizeof(ViewStack));
	memset(st, 0, sizeof(ViewStack));
	st->reg_stacks = NewChain();
	traversable_setup(st, node, sr->compositor);
	Node_SetPrivate(node, st);
	Node_SetRenderFunction(node, RenderNavigationInfo);
	Node_SetPreDestroyFunction(node, DestroyViewStack);
	((M_NavigationInfo*)node)->on_set_bind = navinfo_set_bind;
}


static void fog_set_bind(SFNode *node)
{
	ViewStack *st = (ViewStack *) Node_GetPrivate(node);
	Bindable_OnSetBind(node, st->reg_stacks);
	SR_Invalidate(st->compositor, NULL);
}

static void RenderFog(SFNode *node, void *rs)
{
	Fixed density, vrange;
	SFVec3f start, end;
	RenderEffect3D *eff = (RenderEffect3D *)rs;
	M_Fog *fog = (M_Fog *) node;
	ViewStack *st = (ViewStack *) Node_GetPrivate(node);

	if (!eff->fogs) return;

	/*first traverse, bound if needed*/
	if (ChainFindEntry(eff->fogs, node) < 0) {
		ChainAddEntry(eff->fogs, node);
		if (ChainGetEntry(eff->fogs, 0) == fog) {
			if (!fog->isBound) Bindable_SetIsBound(node, 1);
		}
		assert(ChainFindEntry(st->reg_stacks, eff->fogs)==-1);
		ChainAddEntry(st->reg_stacks, eff->fogs);

		m4_mx_copy(st->world_view_mx, eff->model_matrix);
		/*in any case don't draw the first time*/
		SR_Invalidate(st->compositor, NULL);
		return;
	}
	/*not rendering, return*/
	if (eff->traversing_mode != TRAVERSE_RENDER_BINDABLE) {
		if (eff->traversing_mode==TRAVERSE_SORT) m4_mx_copy(st->world_view_mx, eff->model_matrix);
		return;
	}
	/*not bound*/
	if (!fog->isBound || !fog->visibilityRange) return;

	start.x = start.y = start.z = 0;
	end.x = end.y = end.z = fog->visibilityRange;
	m4_mx_apply_vec(&st->world_view_mx, &start);
	m4_mx_apply_vec(&st->world_view_mx, &end);
	m4_vec_diff(end, end, start);
	vrange = m4_vec_len(end);

	density = m4_divfix(FIX_ONE, vrange);
	VS3D_SetFog(eff->surface, fog->fogType.buffer, fog->color, density, vrange);
}

void R3D_InitFog(Render3D *sr, SFNode *node)
{
	ViewStack *st = malloc(sizeof(ViewStack));
	memset(st, 0, sizeof(ViewStack));
	st->reg_stacks = NewChain();
	traversable_setup(st, node, sr->compositor);
	Node_SetPrivate(node, st);
	Node_SetPreDestroyFunction(node, DestroyViewStack);
	Node_SetRenderFunction(node, RenderFog);
	((M_Fog*)node)->on_set_bind = fog_set_bind;
}

