/*
 *			GPAC - Multimedia Framework C SDK
 *
 *			Authors: Jean Le Feuvre
 *			Copyright (c) Telecom ParisTech 2000-2023
 *					All rights reserved
 *
 *  This file is part of GPAC / Scene Compositor sub-project
 *
 *  GPAC is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; see the file COPYING.  If not, write to
 *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

#include "nodes_stacks.h"
#include "visual_manager.h"

#if !defined(GPAC_DISABLE_COMPOSITOR)

#ifndef GPAC_DISABLE_3D

static void camera_changed(GF_Compositor *compositor, GF_Camera *cam)
{
	cam->flags |= CAM_IS_DIRTY;
	gf_sc_invalidate(compositor, NULL);
	if (compositor->active_layer) gf_node_dirty_set(compositor->active_layer, 0, 1);
}

#endif

static void nav_set_zoom_trans_2d(GF_VisualManager *visual, Fixed zoom, Fixed dx, Fixed dy)
{
	compositor_2d_set_user_transform(visual->compositor, zoom, visual->compositor->trans_x + dx, visual->compositor->trans_y + dy, 0);
#ifndef GPAC_DISABLE_3D
	if (visual->type_3d) camera_changed(visual->compositor, &visual->camera);
#endif
}


#ifndef GPAC_DISABLE_3D

/*shortcut*/
static void gf_mx_rotation_matrix(GF_Matrix *mx, SFVec3f axis_pt, SFVec3f axis, Fixed angle)
{
	gf_mx_init(*mx);
	gf_mx_add_translation(mx, axis_pt.x, axis_pt.y, axis_pt.z);
	gf_mx_add_rotation(mx, angle, axis.x, axis.y, axis.z);
	gf_mx_add_translation(mx, -axis_pt.x, -axis_pt.y, -axis_pt.z);
}

static void view_orbit_x(GF_Compositor *compositor, GF_Camera *cam, Fixed dx)
{
	GF_Matrix mx;
	if (!dx) return;
	gf_mx_rotation_matrix(&mx, cam->target, cam->up, dx);
	gf_mx_apply_vec(&mx, &cam->position);
	camera_changed(compositor, cam);
}
static void view_orbit_y(GF_Compositor *compositor, GF_Camera *cam, Fixed dy)
{
	GF_Matrix mx;
	SFVec3f axis;
	if (!dy) return;
	axis = camera_get_right_dir(cam);
	gf_mx_rotation_matrix(&mx, cam->target, axis, dy);
	gf_mx_apply_vec(&mx, &cam->position);
	/*update up vector*/
	cam->up = gf_vec_cross(camera_get_pos_dir(cam), axis);
	gf_vec_norm(&cam->up);
	camera_changed(compositor, cam);
}

static void view_exam_x(GF_Compositor *compositor, GF_Camera *cam, Fixed dx)
{
	GF_Matrix mx;
	if (!dx) return;
	gf_mx_rotation_matrix(&mx, cam->examine_center, cam->up, dx);
	gf_mx_apply_vec(&mx, &cam->position);
	gf_mx_apply_vec(&mx, &cam->target);
	camera_changed(compositor, cam);
}

static void view_exam_y(GF_Compositor *compositor, GF_Camera *cam, Fixed dy)
{
	GF_Matrix mx;
	SFVec3f axis;
	if (!dy) return;
	axis = camera_get_right_dir(cam);
	gf_mx_rotation_matrix(&mx, cam->examine_center, axis, dy);
	gf_mx_apply_vec(&mx, &cam->position);
	gf_mx_apply_vec(&mx, &cam->target);
	/*update up vector*/
	cam->up = gf_vec_cross(camera_get_pos_dir(cam), axis);
	gf_vec_norm(&cam->up);
	camera_changed(compositor, cam);
}

static void view_roll(GF_Compositor *compositor, GF_Camera *cam, Fixed dd)
{
	GF_Matrix mx;
	SFVec3f delta;
	if (!dd) return;
	gf_vec_add(delta, cam->target, cam->up);
	gf_mx_rotation_matrix(&mx, cam->target, camera_get_pos_dir(cam), dd);
	gf_mx_apply_vec(&mx, &delta);
	gf_vec_diff(cam->up, delta, cam->target);
	gf_vec_norm(&cam->up);
	camera_changed(compositor, cam);
}

static void update_pan_up(GF_Compositor *compositor, GF_Camera *cam)
{
	SFVec3f axis, dir;
	/*update up vector so that right is always horizontal (no y component)*/
	dir = camera_get_pos_dir(cam);
	axis = camera_get_right_dir(cam);
	axis.y = 0;
	gf_vec_norm(&axis);
	cam->up = gf_vec_cross(dir, axis);
	gf_vec_norm(&cam->up);

	camera_changed(compositor, cam);
}

static void view_pan_x(GF_Compositor *compositor, GF_Camera *cam, Fixed dx)
{
	GF_Matrix mx;
	if (!dx) return;
	gf_mx_rotation_matrix(&mx, cam->position, cam->up, dx);
	gf_mx_apply_vec(&mx, &cam->target);

	update_pan_up(compositor, cam);
}
static void view_pan_y(GF_Compositor *compositor, GF_Camera *cam, Fixed dy)
{
	GF_Matrix mx;
	GF_Vec prev_target = cam->target;
	if (!dy) return;
	gf_mx_rotation_matrix(&mx, cam->position, camera_get_right_dir(cam), dy);
	gf_mx_apply_vec(&mx, &cam->target);
	switch (cam->navigate_mode) {
	case GF_NAVIGATE_WALK:
	case GF_NAVIGATE_VR:
	case GF_NAVIGATE_GAME:
		if (cam->target.z*prev_target.z<0) {
			cam->target = prev_target;
			return;
		}
	default:
		break;
	}

	update_pan_up(compositor, cam);
}

/*for translation moves when jumping*/
#define JUMP_SCALE_FACTOR	4

static void view_translate_x(GF_Compositor *compositor, GF_Camera *cam, Fixed dx)
{
	SFVec3f v;
	if (!dx) return;
	if (cam->jumping) dx *= JUMP_SCALE_FACTOR;
	v = gf_vec_scale(camera_get_right_dir(cam), dx);
	gf_vec_add(cam->target, cam->target, v);
	gf_vec_add(cam->position, cam->position, v);
	camera_changed(compositor, cam);
}
static void view_translate_y(GF_Compositor *compositor, GF_Camera *cam, Fixed dy)
{
	SFVec3f v;
	if (!dy) return;
	if (cam->jumping) dy *= JUMP_SCALE_FACTOR;
	v = gf_vec_scale(cam->up, dy);
	gf_vec_add(cam->target, cam->target, v);
	gf_vec_add(cam->position, cam->position, v);
	camera_changed(compositor, cam);
}

static void view_translate_z(GF_Compositor *compositor, GF_Camera *cam, Fixed dz)
{
	SFVec3f v;
	if (!dz) return;
	if (cam->jumping) dz *= JUMP_SCALE_FACTOR;
	dz = gf_mulfix(dz, cam->speed);
	v = gf_vec_scale(camera_get_target_dir(cam), dz);
	gf_vec_add(cam->target, cam->target, v);
	gf_vec_add(cam->position, cam->position, v);
	camera_changed(compositor, cam);
}

static void view_zoom(GF_Compositor *compositor, GF_Camera *cam, Fixed z)
{
	Fixed oz;
	if ((z>FIX_ONE) || (z<-FIX_ONE)) return;
	oz = gf_divfix(cam->vp_fov, cam->fieldOfView);
	if (oz<FIX_ONE) z/=4;
	oz += z;
	if (oz<=0) return;

	cam->fieldOfView = gf_divfix(cam->vp_fov, oz);
	if (cam->fieldOfView>GF_PI) cam->fieldOfView=GF_PI;
	camera_changed(compositor, cam);
}

Bool gf_sc_fit_world_to_screen(GF_Compositor *compositor)
{
	GF_TraverseState tr_state;
	SFVec3f pos, diff;
	Fixed dist, d;
	GF_Camera *cam;
	GF_Node *top;

#ifndef GPAC_DISABLE_VRML
//	if (gf_list_count(compositor->visual->back_stack)) return;
	if (gf_list_count(compositor->visual->view_stack)) return 0;
#endif

	gf_mx_p(compositor->mx);
	top = gf_sg_get_root_node(compositor->scene);
	if (!top) {
		gf_mx_v(compositor->mx);
		return 0;
	}
	memset(&tr_state, 0, sizeof(GF_TraverseState));
	gf_mx_init(tr_state.model_matrix);
	tr_state.traversing_mode = TRAVERSE_GET_BOUNDS;
	tr_state.visual = compositor->visual;
	gf_node_traverse(top, &tr_state);
	if (gf_node_dirty_get(top)) {
		tr_state.bbox.is_set = 0;
	}

	if (!tr_state.bbox.is_set) {
		gf_mx_v(compositor->mx);
		/*empty world ...*/
		if (tr_state.bbox.radius==-1) return 1;
		/*2D world with 3D camera forced*/
		if (tr_state.bounds.width&&tr_state.bounds.height) return 1;
		return 0;
	}

	cam = &compositor->visual->camera;

	cam->world_bbox = tr_state.bbox;
	/*fit is based on bounding sphere*/
	dist = gf_divfix(tr_state.bbox.radius, gf_sin(cam->fieldOfView/2) );
	gf_vec_diff(diff, cam->center, tr_state.bbox.center);
	/*do not update if camera is outside the scene bounding sphere and dist is too close*/
	if (gf_vec_len(diff) > tr_state.bbox.radius + cam->radius) {
		gf_vec_diff(diff, cam->vp_position, tr_state.bbox.center);
		d = gf_vec_len(diff);
		if (d<dist) {
			gf_mx_v(compositor->mx);
			return 1;
		}
	}

	diff = gf_vec_scale(camera_get_pos_dir(cam), dist);
	gf_vec_add(pos, tr_state.bbox.center, diff);
	diff = cam->position;
	camera_set_vectors(cam, pos, cam->vp_orientation, cam->fieldOfView);
	cam->position = diff;
	camera_move_to(cam, pos, cam->target, cam->up);
	if (!compositor->player) {
		camera_stop_anim(cam);
		camera_set_vectors(cam, cam->end_pos, cam->end_ori, cam->end_fov);
	}

	cam->examine_center = tr_state.bbox.center;
	cam->flags |= CF_STORE_VP;
	if (cam->z_far < dist) cam->z_far = 10*dist;
	camera_changed(compositor, cam);
	gf_mx_v(compositor->mx);
	return 1;
}

static void handle_mouse_move_3d(GF_Compositor *compositor, GF_Camera *cam, u32 keys, Fixed dx, Fixed dy)
{
	Fixed trans_scale = cam->width/20;
	//if default VP is quite far from center use larger dz/dy moves
	if (cam->vp_dist>100) trans_scale *= 10;

	switch (cam->navigate_mode) {
	/*FIXME- we'll likely need a "step" value for walk at some point*/
	case GF_NAVIGATE_WALK:
	case GF_NAVIGATE_FLY:
		view_pan_x(compositor, cam, -dx);
		if (keys & GF_KEY_MOD_CTRL) view_pan_y(compositor, cam, dy);
		else view_translate_z(compositor, cam, gf_mulfix(dy, trans_scale));
		break;
	case GF_NAVIGATE_VR:
		view_pan_x(compositor, cam, -dx);
		if (keys & GF_KEY_MOD_CTRL) view_zoom(compositor, cam, dy);
		else view_pan_y(compositor, cam, dy);
		break;
	case GF_NAVIGATE_PAN:
		view_pan_x(compositor, cam, -dx);
		if (keys & GF_KEY_MOD_CTRL) view_translate_z(compositor, cam, gf_mulfix(dy, trans_scale));
		else view_pan_y(compositor, cam, dy);
		break;
	case GF_NAVIGATE_SLIDE:
		view_translate_x(compositor, cam, gf_mulfix(dx, trans_scale));
		if (keys & GF_KEY_MOD_CTRL) view_translate_z(compositor, cam, gf_mulfix(dy, trans_scale));
		else view_translate_y(compositor, cam, gf_mulfix(dy, trans_scale));
		break;
	case GF_NAVIGATE_EXAMINE:
		if (keys & GF_KEY_MOD_CTRL) {
			view_translate_z(compositor, cam, gf_mulfix(dy, trans_scale));
			view_roll(compositor, cam, gf_mulfix(dx, trans_scale));
		} else {
			if (ABS(dx) > ABS(dy)) {
				view_exam_x(compositor, cam, -gf_mulfix(GF_PI, dx));
			} else {
				view_exam_y(compositor, cam, gf_mulfix(GF_PI, dy));
			}
		}
		break;
	case GF_NAVIGATE_ORBIT:
		if (keys & GF_KEY_MOD_CTRL) {
			view_translate_z(compositor, cam, gf_mulfix(dy, trans_scale));
		} else {
			view_orbit_x(compositor, cam, -gf_mulfix(GF_PI, dx));
			view_orbit_y(compositor, cam, gf_mulfix(GF_PI, dy));
		}
		break;
	case GF_NAVIGATE_GAME:
		view_pan_x(compositor, cam, -dx);
		view_pan_y(compositor, cam, dy);
		break;
	}
}

static Bool compositor_handle_navigation_3d(GF_Compositor *compositor, GF_Event *ev)
{
	Fixed x, y, trans_scale;
	Fixed dx, dy, key_trans, key_pan, key_exam;
	s32 key_inv;
	u32 keys;
	GF_Camera *cam;
	Fixed zoom = compositor->zoom;

	cam = NULL;
#ifndef GPAC_DISABLE_VRML
	if (compositor->active_layer) {
		cam = compositor_layer3d_get_camera(compositor->active_layer);
	}
#endif

	if (!cam) {
		cam = &compositor->visual->camera;
	}
	if (!cam || (cam->navigate_mode==GF_NAVIGATE_NONE)) return 0;

	keys = compositor->key_states;
	if (!cam->navigate_mode && !(keys & GF_KEY_MOD_ALT) ) return 0;
	x = y = 0;
	/*renorm between -1, 1*/
	if (ev->type <= GF_EVENT_LAST_MOUSE) {
		x = gf_divfix( INT2FIX(ev->mouse.x - (s32) compositor->visual->width/2), INT2FIX(compositor->visual->width));
		y = gf_divfix( INT2FIX(ev->mouse.y - (s32) compositor->visual->height/2), INT2FIX(compositor->visual->height));
	}

	dx = (x - compositor->grab_x);
	dy = (compositor->grab_y - y);

	trans_scale = cam->width/20;
	key_trans = cam->avatar_size.x/2;
	//if default VP is quite far from center use larger dz/dy moves
	if (cam->vp_dist>100) trans_scale *= 10;

	if (cam->world_bbox.is_set && (key_trans*5 > cam->world_bbox.radius)) {
		key_trans = cam->world_bbox.radius / 100;
	}

	key_pan = FIX_ONE/20;
	key_exam = FIX_ONE/20;
	key_inv = 1;

	if (keys & GF_KEY_MOD_SHIFT) {
		dx *= 4;
		dy *= 4;
		key_pan *= 4;
		key_exam *= 4;
		key_trans*=4;
	}

	if (! compositor->orientation_sensors_active) {
		Fixed yaw, pitch, roll;
		gf_mx_get_yaw_pitch_roll(&compositor->visual->camera.modelview, &yaw, &pitch, &roll);
		compositor->audio_renderer->yaw = yaw;
		compositor->audio_renderer->pitch = pitch;
		compositor->audio_renderer->roll = roll;
	}

	switch (ev->type) {
	case GF_EVENT_MOUSEDOWN:
		/*left*/
		if (ev->mouse.button==GF_MOUSE_LEFT) {
			compositor->grab_x = x;
			compositor->grab_y = y;
			compositor->navigation_state = 1;
			compositor->auto_rotate=0;

			/*change vp and examine center to current location*/
			if ((keys & GF_KEY_MOD_CTRL) && compositor->hit_square_dist) {
				cam->vp_position = cam->position;
				cam->vp_orientation = camera_get_orientation(cam->position, cam->target, cam->up);
				cam->vp_fov = cam->fieldOfView;
				cam->examine_center = compositor->hit_world_point;
				camera_changed(compositor, cam);
				return 1;
			}
		}
		/*right*/
		else if (ev->mouse.button==GF_MOUSE_RIGHT) {
			if (compositor->navigation_state && (cam->navigate_mode==GF_NAVIGATE_WALK)) {
				camera_jump(cam);
				gf_sc_invalidate(compositor, NULL);
				return 1;
			}
			else if (keys & GF_KEY_MOD_CTRL) gf_sc_fit_world_to_screen(compositor);
		}
		break;

	case GF_EVENT_MOUSEMOVE:
		if (compositor->orientation_sensors_active) return 0;

		if (!compositor->navigation_state) {
			if (cam->navigate_mode==GF_NAVIGATE_GAME) {
				/*init mode*/
				compositor->grab_x = x;
				compositor->grab_y = y;
				compositor->navigation_state = 1;
			}
			compositor->auto_rotate=0;
			return 0;
		}
		compositor->navigation_state++;

		if (x <= -0.49) compositor->auto_rotate = 1;
		else if (x >= 0.49) compositor->auto_rotate = 2;
		else if (y <= -0.49) compositor->auto_rotate = 3;
		else if (y >= 0.49) compositor->auto_rotate = 4;
		else
			compositor->auto_rotate = 0;

		handle_mouse_move_3d(compositor, cam, keys, dx, dy);

		compositor->grab_x = x;
		compositor->grab_y = y;
		return 1;

	case GF_EVENT_MOUSEWHEEL:
		switch (cam->navigate_mode) {
		/*FIXME- we'll likely need a "step" value for walk at some point*/
		case GF_NAVIGATE_WALK:
		case GF_NAVIGATE_FLY:
			view_pan_y(compositor, cam, gf_mulfix(key_pan, ev->mouse.wheel_pos));
			break;
		case GF_NAVIGATE_VR:
			view_zoom(compositor, cam, gf_mulfix(key_pan, ev->mouse.wheel_pos));
			break;
		case GF_NAVIGATE_SLIDE:
		case GF_NAVIGATE_EXAMINE:
		case GF_NAVIGATE_ORBIT:
		case GF_NAVIGATE_PAN:
			if (cam->is_3D) {
				view_translate_z(compositor, cam, gf_mulfix(trans_scale, ev->mouse.wheel_pos) * ((keys & GF_KEY_MOD_SHIFT) ? 4 : 1));
			} else {
				nav_set_zoom_trans_2d(compositor->visual, zoom + INT2FIX(ev->mouse.wheel_pos)/10, 0, 0);
			}
		}
		return 1;

	case GF_EVENT_MULTITOUCH:
		compositor->auto_rotate=0;
		compositor->navigation_state = 0;
		if (ev->mtouch.num_fingers==2) {
			if( ABS(ev->mtouch.pinch) * 100 > 2 ) {
				if (cam->is_3D) {
					view_translate_z(compositor, cam, gf_mulfix(cam->width, compositor->visual->width* ev->mtouch.pinch));
				} else {
					nav_set_zoom_trans_2d(compositor->visual, zoom + gf_mulfix(trans_scale, ev->mtouch.pinch), 0, 0);
				}
				return 1;
			}
			if( ABS(ev->mtouch.rotation) > GF_PI/40 ) {
				view_roll(compositor, cam, gf_mulfix(ev->mtouch.rotation, trans_scale));
				return 1;
			}
		} else if (ev->mtouch.num_fingers==3) {
			compositor->visual->camera.start_zoom = compositor->zoom;
			compositor->zoom = FIX_ONE;
			compositor->interoccular_offset = 0;
			compositor->focdist = 0;
			compositor->interoccular_offset = 0;
			compositor->focdist = 0;
			compositor_3d_reset_camera(compositor);
			return 1;
		}
		return 0;

	case GF_EVENT_MOUSEUP:
		if (ev->mouse.button==GF_MOUSE_LEFT) {
			compositor->navigation_state = 0;
		}
		break;

	case GF_EVENT_KEYDOWN:
		switch (ev->key.key_code) {
		case GF_KEY_BACKSPACE:
			gf_sc_reset_graphics(compositor);
			return 1;
		case GF_KEY_C:
			compositor->collide_mode = compositor->collide_mode  ? GF_COLLISION_NONE : GF_COLLISION_DISPLACEMENT;
			return 1;
		case GF_KEY_J:
			if (cam->navigate_mode==GF_NAVIGATE_WALK) {
				camera_jump(cam);
				gf_sc_invalidate(compositor, NULL);
				return 1;
			}
			break;
		case GF_KEY_HOME:
			if (!compositor->navigation_state) {
				compositor->visual->camera.start_zoom = compositor->zoom;
				compositor->zoom = FIX_ONE;
				compositor->interoccular_offset = 0;
				compositor->focdist = 0;
				compositor->interoccular_offset = 0;
				compositor->focdist = 0;
				compositor_3d_reset_camera(compositor);
			}
			break;
		case GF_KEY_END:
			if (cam->navigate_mode==GF_NAVIGATE_GAME) {
				cam->navigate_mode = GF_NAVIGATE_WALK;
				compositor->navigation_state = 0;
				return 1;
			}
			break;
		case GF_KEY_LEFT:
			key_inv = -1;
		case GF_KEY_RIGHT:
			if (keys & GF_KEY_MOD_ALT) {
				if ( (keys & GF_KEY_MOD_SHIFT) && (compositor->visual->nb_views > 1) ) {
					/*+ or - 10 cm*/
					compositor->focdist += INT2FIX(key_inv);
					cam->flags |= CAM_IS_DIRTY;
					GF_LOG(GF_LOG_INFO, GF_LOG_COMPOSE, ("AutoStereo view distance %f - focus %f\n", FIX2FLT(compositor->video_out->dispdist)/100, FIX2FLT(compositor->focdist)/100));
					gf_sc_invalidate(compositor, NULL);
					return 1;
				}
				return 0;
			}


			switch (cam->navigate_mode) {
			case GF_NAVIGATE_SLIDE:
				if (keys & GF_KEY_MOD_CTRL) view_pan_x(compositor, cam, key_inv * key_pan);
				else view_translate_x(compositor, cam, key_inv * key_trans);
				break;
			case GF_NAVIGATE_EXAMINE:
				if (keys & GF_KEY_MOD_CTRL) view_roll(compositor, cam, gf_mulfix(dx, trans_scale));
				else view_exam_x(compositor, cam, -key_inv * key_exam);
				break;
			case GF_NAVIGATE_ORBIT:
				if (keys & GF_KEY_MOD_CTRL) view_translate_x(compositor, cam, key_inv * key_trans);
				else view_orbit_x(compositor, cam, -key_inv * key_exam);
				break;
			case GF_NAVIGATE_GAME:
				view_translate_x(compositor, cam, key_inv * key_trans);
				break;
			case GF_NAVIGATE_VR:
				view_pan_x(compositor, cam, -key_inv * key_pan);
				break;
			/*walk/fly/pan*/
			default:
				if (keys & GF_KEY_MOD_CTRL) view_translate_x(compositor, cam, key_inv * key_trans);
				else view_pan_x(compositor, cam, -key_inv * key_pan);
				break;
			}
			return 1;
		case GF_KEY_DOWN:
			key_inv = -1;
		case GF_KEY_UP:
			if (keys & GF_KEY_MOD_ALT) {
				if ( (keys & GF_KEY_MOD_SHIFT) && (compositor->visual->nb_views > 1) ) {
					compositor->interoccular_offset += FLT2FIX(0.5) * key_inv;
					GF_LOG(GF_LOG_INFO, GF_LOG_COMPOSE, ("AutoStereo interoccular distance %f\n", FIX2FLT(compositor->iod + compositor->interoccular_offset)));
					cam->flags |= CAM_IS_DIRTY;
					gf_sc_invalidate(compositor, NULL);
					return 1;
				}
				return 0;
			}
			switch (cam->navigate_mode) {
			case GF_NAVIGATE_SLIDE:
				if (keys & GF_KEY_MOD_CTRL) view_translate_z(compositor, cam, key_inv * key_trans);
				else view_translate_y(compositor, cam, key_inv * key_trans);
				break;
			case GF_NAVIGATE_EXAMINE:
				if (keys & GF_KEY_MOD_CTRL) view_translate_z(compositor, cam, key_inv * key_trans);
				else view_exam_y(compositor, cam, -key_inv * key_exam);
				break;
			case GF_NAVIGATE_ORBIT:
				if (keys & GF_KEY_MOD_CTRL) view_translate_y(compositor, cam, key_inv * key_trans);
				else view_orbit_y(compositor, cam, -key_inv * key_exam);
				break;
			case GF_NAVIGATE_PAN:
				if (keys & GF_KEY_MOD_CTRL) view_translate_y(compositor, cam, key_inv * key_trans);
				else view_pan_y(compositor, cam, key_inv * key_pan);
				break;
			case GF_NAVIGATE_GAME:
				view_translate_z(compositor, cam, key_inv * key_trans);
				break;
			case GF_NAVIGATE_VR:
				if (keys & GF_KEY_MOD_CTRL) view_zoom(compositor, cam, key_inv * key_pan);
				else view_pan_y(compositor, cam, key_inv * key_pan);
				break;
			/*walk/fly*/
			default:
				if (keys & GF_KEY_MOD_CTRL) view_pan_y(compositor, cam, key_inv * key_pan);
				else view_translate_z(compositor, cam, key_inv * key_trans);
				break;
			}
			return 1;

		case GF_KEY_PAGEDOWN:
			if (keys & GF_KEY_MOD_CTRL) {
				view_zoom(compositor, cam, FIX_ONE/10);
				return 1;
			}
			break;
		case GF_KEY_PAGEUP:
			if (keys & GF_KEY_MOD_CTRL) {
				view_zoom(compositor, cam, -FIX_ONE/10);
				return 1;
			}
			break;
		case GF_KEY_D:
			if (keys & GF_KEY_MOD_CTRL) {
				if (compositor->tvtd==TILE_DEBUG_FULL) compositor->tvtd = 0;
				else compositor->tvtd ++;
				gf_sc_invalidate(compositor, NULL);
				return 1;
			}
			break;
		case GF_KEY_G:
			if (keys & GF_KEY_MOD_CTRL) {
				compositor->vrhud_mode++;
				if (compositor->vrhud_mode==5) compositor->vrhud_mode=0;
				gf_sc_invalidate(compositor, NULL);
				return 1;
			}
			break;
		case GF_KEY_A:
			if (keys & GF_KEY_MOD_CTRL) {
				compositor->tvtf = !compositor->tvtf;
				gf_sc_invalidate(compositor, NULL);
				return 1;
			}
			break;
		}
		break;
	case GF_EVENT_SENSOR_ORIENTATION:
	{
		Fixed z, w, yaw, /*pitch, */roll;
		GF_Vec target;
		GF_Matrix mx;

		/* In iOS we get x, y, z in quaternions (first measurement is the frame of reference) */
		if (ev->sensor.w) {
			x = ev->sensor.x;
			y = ev->sensor.y;
			z = ev->sensor.z;
			w = ev->sensor.w;
		
			yaw = gf_atan2(2*gf_mulfix(z,w) - 2*gf_mulfix(y,x) , 1 - 2*gf_mulfix(z,z) - 2*gf_mulfix(x,x));
			//pitch = asin(2*y*z + 2*x*w);
			roll = gf_atan2(2*gf_mulfix(y,w) - 2*gf_mulfix(z,x) , 1 - 2*gf_mulfix(y,y) - 2*gf_mulfix(x,x));
		} else {
			/*
			 * In Android we get yaw, pitch, roll values (in rad)
			 * The frame of reference is absolute
			 */
			yaw = ev->sensor.x;
			//pitch = ev->sensor.y;
			roll = ev->sensor.z;
		}
		target.x = 0;
		target.y = -FIX_ONE;
		target.z = 0;
		gf_mx_init(mx);
		gf_mx_add_rotation(&mx, yaw, 0, FIX_ONE, 0);
		gf_mx_add_rotation(&mx, -roll, FIX_ONE, 0, 0);
		
		gf_mx_apply_vec(&mx, &target);

		cam->target = target;
		update_pan_up(compositor, cam);
	}
		return 1;
	}
	return 0;
}

#endif


static Bool compositor_handle_navigation_2d(GF_VisualManager *visual, GF_Event *ev)
{
	Fixed x, y, dx, dy, key_trans, key_rot, zoom, new_zoom;
	u32 navigation_mode;
	s32 key_inv;
	Bool is_pixel_metrics = visual->compositor->traverse_state->pixel_metrics;
	u32 keys = visual->compositor->key_states;

	zoom = visual->compositor->zoom;
	navigation_mode = visual->compositor->navigate_mode;
#ifndef GPAC_DISABLE_3D
	if (visual->type_3d) navigation_mode = visual->camera.navigate_mode;
#endif

	if (navigation_mode==GF_NAVIGATE_NONE) return 0;
	if (!navigation_mode && !(keys & GF_KEY_MOD_ALT) ) return 0;


	x = y = 0;
	/*renorm between -1, 1*/
	if (ev->type <= GF_EVENT_LAST_MOUSE) {
		x = INT2FIX(ev->mouse.x);
		y = INT2FIX(ev->mouse.y);
	}
	dx = x - visual->compositor->grab_x;
	if (visual->center_coords) {
		dy = visual->compositor->grab_y - y;
	} else {
		dy = y - visual->compositor->grab_y;
	}
	if (!is_pixel_metrics) {
		dx /= visual->width;
		dy /= visual->height;
	}

	key_inv = 1;
	key_trans = INT2FIX(2);
	key_rot = GF_PI/100;

	if (keys & GF_KEY_MOD_SHIFT) {
		dx *= 4;
		dy *= 4;
		key_rot *= 4;
		key_trans*=4;
	}

	if (!is_pixel_metrics) {
		key_trans /= visual->width;
	}

	switch (ev->type) {
	case GF_EVENT_MOUSEDOWN:
		/*left*/
		if (ev->mouse.button==GF_MOUSE_LEFT) {
			visual->compositor->grab_x = x;
			visual->compositor->grab_y = y;
			visual->compositor->navigation_state = 1;
			/*update zoom center*/
			if (keys & GF_KEY_MOD_CTRL) {
				visual->compositor->trans_x -= visual->compositor->grab_x - INT2FIX(visual->width)/2;
				visual->compositor->trans_y += INT2FIX(visual->height)/2 - visual->compositor->grab_y;
				nav_set_zoom_trans_2d(visual, visual->compositor->zoom, 0, 0);
			}
			return 0;
		}
		break;

	case GF_EVENT_MOUSEUP:
		if (ev->mouse.button==GF_MOUSE_LEFT) {
			visual->compositor->navigation_state = 0;
			return 0;
		}
		break;
	case GF_EVENT_MULTITOUCH:
		if (ev->mtouch.num_fingers==2) {
			if( ABS(ev->mtouch.pinch) * 100 > 2 ) {
				new_zoom = zoom + ev->mtouch.pinch * MIN(visual->width, visual->height)/100;
				nav_set_zoom_trans_2d(visual, new_zoom, 0, 0);
				return 1;
			}
			if( ABS(ev->mtouch.rotation) > GF_PI/40 ) {
				visual->compositor->rotation -= ev->mtouch.rotation;
				nav_set_zoom_trans_2d(visual, zoom, 0, 0);
				return 1;
			}
		} else if (ev->mtouch.num_fingers==3) {
			visual->compositor->trans_x = visual->compositor->trans_y = 0;
			visual->compositor->rotation = 0;
			visual->compositor->zoom = FIX_ONE;
			nav_set_zoom_trans_2d(visual, FIX_ONE, 0, 0);
			return 1;
		}
		return 0;

	case GF_EVENT_MOUSEWHEEL:
		switch (navigation_mode) {
		case GF_NAVIGATE_SLIDE:
			new_zoom = zoom + INT2FIX(ev->mouse.wheel_pos)/10;
			nav_set_zoom_trans_2d(visual, new_zoom, 0, 0);
			return 1;
		case GF_NAVIGATE_EXAMINE:
			if (ev->mouse.wheel_pos>0)
				visual->compositor->rotation += gf_asin( GF_PI / 10);
			else
				visual->compositor->rotation -= gf_asin( GF_PI / 10);
			nav_set_zoom_trans_2d(visual, zoom, 0, 0);
			return 1;
		}
		return 0;

	case GF_EVENT_MOUSEMOVE:
		if (!visual->compositor->navigation_state) return 0;
		visual->compositor->navigation_state++;
		switch (navigation_mode) {
		case GF_NAVIGATE_SLIDE:
			if (keys & GF_KEY_MOD_CTRL) {
				if (dy) {
					new_zoom = zoom;
					if (new_zoom > FIX_ONE) new_zoom += dy/20;
					else new_zoom += dy/80;
					nav_set_zoom_trans_2d(visual, new_zoom, 0, 0);
				}
			} else {
				nav_set_zoom_trans_2d(visual, zoom, dx, dy);
			}
			break;
		case GF_NAVIGATE_EXAMINE:
		{
			Fixed sin = gf_mulfix(GF_PI, dy) / visual->height;
			//truncate in [-1;1] for arcsin()
			if (sin < -FIX_ONE)
				sin = -FIX_ONE;
			if (sin >  FIX_ONE)
				sin =  FIX_ONE;
			visual->compositor->rotation += gf_asin(sin);
			nav_set_zoom_trans_2d(visual, zoom, 0, 0);
		}
		break;
		}
		visual->compositor->grab_x = x;
		visual->compositor->grab_y = y;
		return 1;
	case GF_EVENT_KEYDOWN:
		switch (ev->key.key_code) {
		case GF_KEY_BACKSPACE:
			gf_sc_reset_graphics(visual->compositor);
			return 1;
		case GF_KEY_HOME:
			if (!visual->compositor->navigation_state) {
				visual->compositor->trans_x = visual->compositor->trans_y = 0;
				visual->compositor->rotation = 0;
				visual->compositor->zoom = FIX_ONE;
				nav_set_zoom_trans_2d(visual, FIX_ONE, 0, 0);
			}
			return 1;
		case GF_KEY_LEFT:
			key_inv = -1;
		case GF_KEY_RIGHT:
			if (navigation_mode == GF_NAVIGATE_SLIDE) {
				nav_set_zoom_trans_2d(visual, zoom, key_inv*key_trans, 0);
			}
			else {
				visual->compositor->rotation -= key_inv * key_rot;
				nav_set_zoom_trans_2d(visual, zoom, 0, 0);
			}
			return 1;
		case GF_KEY_DOWN:
			key_inv = -1;
		case GF_KEY_UP:
			if (navigation_mode == GF_NAVIGATE_SLIDE) {
				if (keys & GF_KEY_MOD_CTRL) {
					new_zoom = zoom;
					if (new_zoom > FIX_ONE) new_zoom += key_inv*FIX_ONE/10;
					else new_zoom += key_inv*FIX_ONE/20;
					nav_set_zoom_trans_2d(visual, new_zoom, 0, 0);
				} else {
					nav_set_zoom_trans_2d(visual, zoom, 0, key_inv*key_trans);
				}
			}
			else {
				visual->compositor->rotation += key_inv*key_rot;
				nav_set_zoom_trans_2d(visual, zoom, 0, 0);
			}
			return 1;
		}
		break;
	}
	return 0;
}

void compositor_handle_auto_navigation(GF_Compositor *compositor)
{
#ifndef GPAC_DISABLE_3D
	GF_Camera *cam = NULL;
	Fixed dx, dy;
#ifndef GPAC_DISABLE_VRML
	if (compositor->active_layer) {
		cam = compositor_layer3d_get_camera(compositor->active_layer);
	}
	if (!cam)
		cam = &compositor->visual->camera;
#endif

	dx = dy = 0;
	switch (compositor->auto_rotate) {
	case 1:
		dx = -FIX_ONE/100;
		break;
	case 2:
		dx = FIX_ONE/100;
		break;
	case 3:
		dy = FIX_ONE/100;
		if (cam->up.z>0.9999) compositor->auto_rotate=4;
		break;
	case 4:
		dy = -FIX_ONE/100;
		if (cam->up.z<-0.9999) compositor->auto_rotate=3;
		break;
	default:
		return;
	}
	if (compositor->gazer_enabled)
		compositor->gaze_changed = GF_FALSE;
	handle_mouse_move_3d(compositor, cam, 0, dx, dy);
#endif
}

Bool compositor_handle_navigation(GF_Compositor *compositor, GF_Event *ev)
{
	if (!compositor->scene) return 0;
#ifndef GPAC_DISABLE_3D
	if ( (compositor->visual->type_3d>0) || compositor->active_layer)
		return compositor_handle_navigation_3d(compositor, ev);
#endif
	return compositor_handle_navigation_2d(compositor->visual, ev);
}

#endif //!defined(GPAC_DISABLE_COMPOSITOR)
