/*
 *			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"
/*for anchor*/
#include "grouping.h"

typedef struct
{
	SFNode *owner;
	struct scene_renderer *compositor;
	GROUPINGNODESTACK
	Bool enabled, active;
	SensorHandler hdl;
} AnchorStack;

static void DestroyAnchor(SFNode *n)
{
	AnchorStack *st = (AnchorStack*)Node_GetPrivate(n);
	R3D_SensorDeleted(st->compositor, &st->hdl);
	DeleteGroupingNode((GroupingNode *)st);
	free(st);
}

static void RenderAnchor(SFNode *node, void *rs)
{
	AnchorStack *st = (AnchorStack *) Node_GetPrivate(node);
	RenderEffect3D *eff = rs;

	if (!st->compositor->user->EventProc) {
		st->enabled = 0;
		return;
	}

	/*note we don't clear dirty flag, this is done in traversing*/
	if (Node_GetDirty(node) & SG_NODE_DIRTY) {
		MFURL *url;
		if (Node_GetTag(node)==TAG_MPEG4_Anchor) {
			url = & ((M_Anchor *)node)->url;
		} else {
			url = & ((X_Anchor *)node)->url;
		}
		st->enabled = 0;
		if (url->count && url->vals[0].url && strlen(url->vals[0].url) )
			st->enabled = 1;
	}

	grouping_traverse((GroupingNode*)st, eff, NULL);
}

static void OnAnchor(SensorHandler *sh, Bool is_over, u32 eventType, RayHitInfo *hit_info)
{
	MFURL *url;
	AnchorStack *st = (AnchorStack *) Node_GetPrivate(sh->owner);

	if (eventType==M4E_LEFTDOWN) st->active = 1;
	else if (st->active && (eventType==M4E_LEFTUP) ) {
		u32 i;
		M4Event evt;
		if (Node_GetTag(sh->owner)==TAG_MPEG4_Anchor) {
			url = & ((M_Anchor *)sh->owner)->url;
			evt.navigate.param_count = ((M_Anchor *)sh->owner)->parameter.count;
			evt.navigate.parameters = (const char **) ((M_Anchor *)sh->owner)->parameter.vals;
		} else {
			url = & ((X_Anchor *)sh->owner)->url;
			evt.navigate.param_count = ((X_Anchor *)sh->owner)->parameter.count;
			evt.navigate.parameters = (const char **) ((X_Anchor *)sh->owner)->parameter.vals;
		}
		evt.type = M4E_NAVIGATE;
		i=0;
		while (i<url->count) {
			evt.navigate.to_url = url->vals[i].url;
			if (!evt.navigate.to_url) break;
			/*current scene navigation*/
			if (evt.navigate.to_url[0] == '#') {
				SFNode *bindable;
				evt.navigate.to_url++;
				bindable = SG_FindNodeByName(Node_GetParentGraph(sh->owner), (char *) evt.navigate.to_url);
				if (bindable) {
					Bindable_SetSetBind(bindable, 1);
					break;
				}
			} else if (st->compositor->term) {
				if (Term_ProcessAnchor(sh->owner, &evt))
					break;
			} else if (st->compositor->user->EventProc) {
				if (st->compositor->user->EventProc(st->compositor->user->opaque, &evt))
					break;
			}
			i++;
		}
	}
}

static Bool anchor_is_enabled(SFNode *node)
{
	AnchorStack *st = (AnchorStack *) Node_GetPrivate(node);
	return st->enabled;
}

static void on_activate_anchor(SFNode *node)
{
	UserEvent3D ev;
	SFVec3f pt;
	AnchorStack *st = (AnchorStack *) Node_GetPrivate(node);
	if (!((M_Anchor *)node)->on_activate) return;

	ev.event_type = M4E_LEFTUP;
	pt.x = pt.y = pt.z = 0;
	OnAnchor(&st->hdl, 0, 1, NULL);
}

SensorHandler *r3d_anchor_get_handler(SFNode *n)
{
	AnchorStack *st = (AnchorStack *) Node_GetPrivate(n);
	return &st->hdl;
}


void R3D_InitAnchor(Render3D *sr, SFNode *node)
{
	Chain *children;
	AnchorStack *stack = malloc(sizeof(AnchorStack));
	memset(stack, 0, sizeof(AnchorStack));
	stack->hdl.IsEnabled = anchor_is_enabled;
	stack->hdl.OnUserEvent = OnAnchor;
	stack->hdl.owner = node;
	if (Node_GetTag(node)==TAG_MPEG4_Anchor) {
		((M_Anchor *)node)->on_activate = on_activate_anchor;
		children = ((M_Anchor *)node)->children;
	} else {
		children = ((X_Anchor *)node)->children;
	}
	SetupGroupingNode((GroupingNode*)stack, sr->compositor, node, children);
	sr->compositor->interaction_sensors++;
	Node_SetPrivate(node, stack);
	Node_SetPreDestroyFunction(node, DestroyAnchor);
	Node_SetRenderFunction(node, RenderAnchor);
}


typedef struct 
{
	SensorHandler hdl;
	struct scene_renderer *compositor;
	Fixed start_angle;
	M4Matrix initial_matrix;
} DiscSensorStack;

static void DestroyDiscSensor(SFNode *node)
{
	DiscSensorStack *st = (DiscSensorStack *) Node_GetPrivate(node);
	R3D_SensorDeleted(st->compositor, &st->hdl);
	free(st);
}

static Bool ds_is_enabled(SFNode *n)
{
	M_DiscSensor *ds = (M_DiscSensor *)n;
	return (ds->enabled || ds->isActive);
}


static void OnDiscSensor(SensorHandler *sh, Bool is_over, u32 eventType, RayHitInfo *hit_info)
{
	M_DiscSensor *ds = (M_DiscSensor *)sh->owner;
	DiscSensorStack *stack = (DiscSensorStack *) Node_GetPrivate(sh->owner);
	
	if (ds->isActive && (!ds->enabled || (eventType==M4E_LEFTUP)) ) {
		if (ds->autoOffset) {
			ds->offset = ds->rotation_changed;
			/*that's an exposedField*/
			Node_OnEventOutSTR(sh->owner, "offset");
		}
		ds->isActive = 0;
		Node_OnEventOutSTR(sh->owner, "isActive");
		R3D_SetGrabbed(stack->compositor, 0);
	}
	else if ((eventType==M4E_LEFTDOWN) && !ds->isActive) {
		/*store inverse matrix*/
		m4_mx_copy(stack->initial_matrix, hit_info->local_to_world);
		stack->start_angle = m4_atan2(hit_info->local_point.x, hit_info->local_point.y);
		ds->isActive = 1;
		Node_OnEventOutSTR(sh->owner, "isActive");
		R3D_SetGrabbed(stack->compositor, 1);
	}
	else if (ds->isActive) {
		M4Ray loc_ray;
		Fixed rot;
		SFVec3f res;
		loc_ray = hit_info->world_ray;
		m4_mx_apply_ray(&stack->initial_matrix, &loc_ray);
		R3D_Get2DPlaneIntersection(&loc_ray, &res);
	
		rot = m4_atan2(res.x, res.y) - stack->start_angle + ds->offset;
		if (ds->minAngle < ds->maxAngle) {
			/*FIXME this doesn't work properly*/
			if (rot < ds->minAngle) rot = ds->minAngle;
			if (rot > ds->maxAngle) rot = ds->maxAngle;
		}
		ds->rotation_changed = rot;
		Node_OnEventOutSTR(sh->owner, "rotation_changed");
	   	ds->trackPoint_changed.x = res.x;
	   	ds->trackPoint_changed.y = res.y;
		Node_OnEventOutSTR(sh->owner, "trackPoint_changed");
	}
}

SensorHandler *r3d_ds_get_handler(SFNode *n)
{
	DiscSensorStack *st = (DiscSensorStack *)Node_GetPrivate(n);
	return &st->hdl;
}

void R3D_InitDiscSensor(Render3D *sr, SFNode *node)
{
	DiscSensorStack *st;
	st = malloc(sizeof(DiscSensorStack));
	memset(st, 0, sizeof(DiscSensorStack));
	st->hdl.IsEnabled = ds_is_enabled;
	st->hdl.OnUserEvent = OnDiscSensor;
	st->hdl.owner = node;
	st->compositor = sr->compositor;
	sr->compositor->interaction_sensors++;
	Node_SetPrivate(node, st);
	Node_SetPreDestroyFunction(node, DestroyDiscSensor);
}


typedef struct 
{
	SFVec2f start_drag;
	M4Matrix initial_matrix;
	struct scene_renderer *compositor;
	SensorHandler hdl;
} PS2DStack;

static void DestroyPlaneSensor2D(SFNode *node)
{
	PS2DStack *st = (PS2DStack *) Node_GetPrivate(node);
	R3D_SensorDeleted(st->compositor, &st->hdl);
	free(st);
}

static Bool ps2D_is_enabled(SFNode *n)
{
	M_PlaneSensor2D *ps2d = (M_PlaneSensor2D *)n;
	return (ps2d->enabled || ps2d->isActive);
}

static void OnPlaneSensor2D(SensorHandler *sh, Bool is_over, u32 eventType, RayHitInfo *hit_info)
{
	M_PlaneSensor2D *ps = (M_PlaneSensor2D *)sh->owner;
	PS2DStack *stack = (PS2DStack *) Node_GetPrivate(sh->owner);

	if (ps->isActive && (!ps->enabled || (eventType==M4E_LEFTUP))) {
		if (ps->autoOffset) {
			ps->offset = ps->translation_changed;
			Node_OnEventOutSTR(sh->owner, "offset");
		}
		ps->isActive = 0;
		Node_OnEventOutSTR(sh->owner, "isActive");
		R3D_SetGrabbed(stack->compositor, 0);
	} else if ((eventType==M4E_LEFTDOWN) && !ps->isActive) {
		m4_mx_copy(stack->initial_matrix, hit_info->local_to_world);
		stack->start_drag.x = hit_info->local_point.x - ps->offset.x;
		stack->start_drag.y = hit_info->local_point.y - ps->offset.y;
		ps->isActive = 1;
		Node_OnEventOutSTR(sh->owner, "isActive");
		R3D_SetGrabbed(stack->compositor, 1);
	} else if (ps->isActive) {
		M4Ray loc_ray;
		SFVec3f res;
		loc_ray = hit_info->world_ray;
		m4_mx_apply_ray(&stack->initial_matrix, &loc_ray);

		R3D_Get2DPlaneIntersection(&loc_ray, &res);
		ps->trackPoint_changed.x = res.x;
		ps->trackPoint_changed.y = res.y;
		Node_OnEventOutSTR(sh->owner, "trackPoint_changed");

		res.x -= stack->start_drag.x;
		res.y -= stack->start_drag.y;
		/*clip*/
		if (ps->minPosition.x <= ps->maxPosition.x) {
			if (res.x < ps->minPosition.x) res.x = ps->minPosition.x;
			if (res.x > ps->maxPosition.x) res.x = ps->maxPosition.x;
		}
		if (ps->minPosition.y <= ps->maxPosition.y) {
			if (res.y < ps->minPosition.y) res.y = ps->minPosition.y;
			if (res.y > ps->maxPosition.y) res.y = ps->maxPosition.y;
		}
		ps->translation_changed.x = res.x;
		ps->translation_changed.y = res.y;
		Node_OnEventOutSTR(sh->owner, "translation_changed");
	}
}

SensorHandler *r3d_ps2D_get_handler(SFNode *n)
{
	PS2DStack *st = (PS2DStack *)Node_GetPrivate(n);
	return &st->hdl;
}

void R3D_InitPlaneSensor2D(Render3D *sr, SFNode *node)
{
	PS2DStack *st = malloc(sizeof(PS2DStack));
	memset(st, 0, sizeof(PS2DStack));
	st->hdl.IsEnabled = ps2D_is_enabled;
	st->hdl.OnUserEvent = OnPlaneSensor2D;
	st->hdl.owner = node;
	st->compositor = sr->compositor;
	st->compositor->interaction_sensors++;
	Node_SetPrivate(node, st);
	Node_SetPreDestroyFunction(node, DestroyPlaneSensor2D);
}


typedef struct 
{
	Double last_time;
	struct scene_renderer *compositor;
	SensorHandler hdl;
} Prox2DStack;

static void DestroyProximitySensor2D(SFNode *node)
{
	Prox2DStack *st = (Prox2DStack *) Node_GetPrivate(node);
	R3D_SensorDeleted(st->compositor, &st->hdl);
	free(st);
}

static Bool prox2D_is_enabled(SFNode *n)
{
	return ((M_ProximitySensor2D *) n)->enabled;
}

static Bool prox2D_is_in_sensor(Prox2DStack *st, M_ProximitySensor2D *ps, Fixed X, Fixed Y)
{
	if (X < ps->center.x - ps->size.x/2) return 0;
	if (X > ps->center.x + ps->size.x/2) return 0;
	if (Y < ps->center.y - ps->size.y/2) return 0;
	if (Y > ps->center.y + ps->size.y/2) return 0;
	return 1;
}

static void OnProximitySensor2D(SensorHandler *sh, Bool is_over, u32 eventType, RayHitInfo *hit_info)
{
	M_ProximitySensor2D *ps = (M_ProximitySensor2D *)sh->owner;
	Prox2DStack *stack = (Prox2DStack *) Node_GetPrivate(sh->owner);
	
	assert(ps->enabled);
	
	if (is_over) {
		stack->last_time = Node_GetSceneTime(sh->owner);
		if (prox2D_is_in_sensor(stack, ps, hit_info->local_point.x, hit_info->local_point.y)) {
			ps->position_changed.x = hit_info->local_point.x;
			ps->position_changed.y = hit_info->local_point.y;
			Node_OnEventOutSTR(sh->owner, "position_changed");

			if (!ps->isActive) {
				ps->isActive = 1;
				Node_OnEventOutSTR(sh->owner, "isActive");
				ps->enterTime = stack->last_time;
				Node_OnEventOutSTR(sh->owner, "enterTime");
			}
			return;
		}
	} 
	/*either we're not over the shape or we're not in sensor*/
	if (ps->isActive) {
		ps->exitTime = stack->last_time;
		Node_OnEventOutSTR(sh->owner, "exitTime");
		ps->isActive = 0;
		Node_OnEventOutSTR(sh->owner, "isActive");
	}
}

SensorHandler *r3d_prox2D_get_handler(SFNode *n)
{
	Prox2DStack *st = (Prox2DStack *)Node_GetPrivate(n);
	return &st->hdl;
}


void R3D_InitProximitySensor2D(Render3D *sr, SFNode *node)
{
	Prox2DStack *st = malloc(sizeof(Prox2DStack));
	memset(st, 0, sizeof(Prox2DStack));
	st->hdl.IsEnabled = prox2D_is_enabled;
	st->hdl.OnUserEvent = OnProximitySensor2D;
	st->hdl.owner = node;
	st->compositor = sr->compositor;
	st->compositor->interaction_sensors++;
	Node_SetPrivate(node, st);
	Node_SetPreDestroyFunction(node, DestroyProximitySensor2D);
}

typedef struct 
{
	SensorHandler hdl;
	Bool mouse_down;
	struct scene_renderer *compositor;
} TouchSensorStack;

static void DestroyTouchSensor(SFNode *node)
{
	TouchSensorStack *st = (TouchSensorStack *) Node_GetPrivate(node);
	R3D_SensorDeleted(st->compositor, &st->hdl);
	free(st);
}

static Bool ts_is_enabled(SFNode *n)
{
	return ((M_TouchSensor *) n)->enabled;
}

static void OnTouchSensor(SensorHandler *sh, Bool is_over, u32 eventType, RayHitInfo *hit_info)
{
	M_TouchSensor *ts = (M_TouchSensor *)sh->owner;
	TouchSensorStack *st = (TouchSensorStack *) Node_GetPrivate(sh->owner);
	assert(ts->enabled);

	/*isActive becomes false, send touch time*/
	if ((eventType==M4E_LEFTUP) && ts->isActive) {
		ts->touchTime = Node_GetSceneTime(sh->owner);
		Node_OnEventOutSTR(sh->owner, "touchTime");
		ts->isActive = 0;
		Node_OnEventOutSTR(sh->owner, "isActive");
		R3D_SetGrabbed(st->compositor, 0);
	}
	if (is_over != ts->isOver) {
		ts->isOver = is_over;
		Node_OnEventOutSTR(sh->owner, "isOver");
	}
	if ((eventType==M4E_LEFTDOWN) && !ts->isActive) {
		ts->isActive = 1;
		Node_OnEventOutSTR(sh->owner, "isActive");
		R3D_SetGrabbed(st->compositor, 1);
	}
	if (is_over) {
		ts->hitPoint_changed = hit_info->local_point;
		Node_OnEventOutSTR(sh->owner, "hitPoint_changed");
		ts->hitNormal_changed = hit_info->hit_normal;
		Node_OnEventOutSTR(sh->owner, "hitNormal_changed");
		ts->hitTexCoord_changed = hit_info->hit_texcoords;
		Node_OnEventOutSTR(sh->owner, "hitTexCoord_changed");
	}
}

SensorHandler *r3d_touch_sensor_get_handler(SFNode *n)
{
	TouchSensorStack *ts = (TouchSensorStack *)Node_GetPrivate(n);
	return &ts->hdl;
}


void R3D_InitTouchSensor(Render3D *sr, SFNode *node)
{
	TouchSensorStack *st = malloc(sizeof(TouchSensorStack));
	memset(st, 0, sizeof(TouchSensorStack));
	st->hdl.IsEnabled = ts_is_enabled;
	st->hdl.OnUserEvent = OnTouchSensor;
	st->hdl.owner = node;
	st->compositor = sr->compositor;
	st->compositor->interaction_sensors++;
	Node_SetPrivate(node, st);
	Node_SetPreDestroyFunction(node, DestroyTouchSensor);
}

void RenderProximitySensor(SFNode *node, void *rs)
{
	SFVec3f user_pos, dist;
	SFRotation ori;
	RenderEffect3D *eff = (RenderEffect3D *)rs;
	M_ProximitySensor *ps = (M_ProximitySensor *)node;

	if (eff->traversing_mode==TRAVERSE_GET_BOUNDS) {
		/*work with twice bigger bbox to get sure we're notify when culled out*/
		m4_vec_add(eff->bbox.max_edge, ps->center, ps->size);
		m4_vec_diff(eff->bbox.min_edge, ps->center, ps->size);
		m4_bbox_refresh(&eff->bbox);
		return;
	} else if (!ps->enabled || (eff->traversing_mode != TRAVERSE_SORT) ) return;

	user_pos = eff->camera->position;
	m4_mx_apply_vec(&eff->model_matrix, &user_pos);
	m4_vec_diff(dist, user_pos, ps->center);

	if (dist.x<0) dist.x *= -1;
	if (dist.y<0) dist.y *= -1;
	if (dist.z<0) dist.z *= -1;

	if ((2*dist.x <= ps->size.x) 
		&& (2*dist.y <= ps->size.y)
		&& (2*dist.z <= ps->size.z) ) {

		if (!ps->isActive) {
			ps->isActive = 1;
			Node_OnEventOutSTR(node, "isActive");
			ps->enterTime = Node_GetSceneTime(node);
			Node_OnEventOutSTR(node, "enterTime");
		}
		if ((ps->position_changed.x != user_pos.x)
			|| (ps->position_changed.y != user_pos.y)
			|| (ps->position_changed.z != user_pos.z) )
		{
			ps->position_changed = user_pos;
			Node_OnEventOutSTR(node, "position_changed");
		}
		dist = eff->camera->target;
		m4_mx_apply_vec(&eff->model_matrix, &dist);
		ori = camera_get_orientation(user_pos, dist, eff->camera->up);
		if ((ori.q != ps->orientation_changed.q)
			|| (ori.x != ps->orientation_changed.x)
			|| (ori.y != ps->orientation_changed.y)
			|| (ori.z != ps->orientation_changed.z) ) {
			ps->orientation_changed = ori;
			Node_OnEventOutSTR(node, "orientation_changed");
		}
	} else if (ps->isActive) {
		ps->isActive = 0;
		Node_OnEventOutSTR(node, "isActive");
		ps->exitTime = Node_GetSceneTime(node);
		Node_OnEventOutSTR(node, "exitTime");
	}
}

void R3D_InitProximitySensor(Render3D *sr, SFNode *node)
{
	Node_SetRenderFunction(node, RenderProximitySensor);
}


typedef struct 
{
	SFVec3f start_drag;
	M4Plane tracker;
	M4Matrix initial_matrix;
	struct scene_renderer *compositor;
	SensorHandler hdl;
} PSStack;

static void DestroyPlaneSensor(SFNode *node)
{
	PSStack *st = (PSStack *) Node_GetPrivate(node);
	R3D_SensorDeleted(st->compositor, &st->hdl);
	free(st);
}

static Bool ps_is_enabled(SFNode *n)
{
	M_PlaneSensor *ps = (M_PlaneSensor *)n;
	return (ps->enabled || ps->isActive);
}

static void OnPlaneSensor(SensorHandler *sh, Bool is_over, u32 eventType, RayHitInfo *hit_info)
{
	M_PlaneSensor *ps = (M_PlaneSensor *)sh->owner;
	PSStack *stack = (PSStack *) Node_GetPrivate(sh->owner);
	
	
	if (ps->isActive && (!ps->enabled || (eventType==M4E_LEFTUP))) {
		if (ps->autoOffset) {
			ps->offset = ps->translation_changed;
			Node_OnEventOutSTR(sh->owner, "offset");
		}
		ps->isActive = 0;
		Node_OnEventOutSTR(sh->owner, "isActive");
		R3D_SetGrabbed(stack->compositor, 0);
	}
	else if ((eventType==M4E_LEFTDOWN) && !ps->isActive) {
		m4_mx_copy(stack->initial_matrix, hit_info->local_to_world);
		m4_vec_diff(stack->start_drag, hit_info->local_point, ps->offset);
		stack->tracker.normal.x = stack->tracker.normal.y = 0; stack->tracker.normal.z = FIX_ONE;
		stack->tracker.d = - m4_vec_dot(stack->start_drag, stack->tracker.normal);
		ps->isActive = 1;
		Node_OnEventOutSTR(sh->owner, "isActive");
		R3D_SetGrabbed(stack->compositor, 1);
	}
	else if (ps->isActive) {
		M4Ray loc_ray;
		SFVec3f res;
		loc_ray = hit_info->world_ray;
		m4_mx_apply_ray(&stack->initial_matrix, &loc_ray);
		m4_plane_intersect_line(&stack->tracker, &loc_ray.orig, &loc_ray.dir, &res);
		ps->trackPoint_changed = res;
		Node_OnEventOutSTR(sh->owner, "trackPoint_changed");

		m4_vec_diff(res, res, stack->start_drag);
		/*clip*/
		if (ps->minPosition.x <= ps->maxPosition.x) {
			if (res.x < ps->minPosition.x) res.x = ps->minPosition.x;
			if (res.x > ps->maxPosition.x) res.x = ps->maxPosition.x;
		}
		if (ps->minPosition.y <= ps->maxPosition.y) {
			if (res.y < ps->minPosition.y) res.y = ps->minPosition.y;
			if (res.y > ps->maxPosition.y) res.y = ps->maxPosition.y;
		}
		ps->translation_changed = res;
		Node_OnEventOutSTR(sh->owner, "translation_changed");
	}
}

SensorHandler *r3d_ps_get_handler(SFNode *n)
{
	PSStack *st = (PSStack *)Node_GetPrivate(n);
	return &st->hdl;
}

void R3D_InitPlaneSensor(Render3D *sr, SFNode *node)
{
	PSStack *st = malloc(sizeof(PSStack));
	memset(st, 0, sizeof(PSStack));
	st->hdl.IsEnabled = ps_is_enabled;
	st->hdl.OnUserEvent = OnPlaneSensor;
	st->hdl.owner = node;
	st->compositor = sr->compositor;
	st->compositor->interaction_sensors++;
	Node_SetPrivate(node, st);
	Node_SetPreDestroyFunction(node, DestroyPlaneSensor);
}

typedef struct 
{
	SensorHandler hdl;
	struct scene_renderer *compositor;
	M4Matrix init_matrix;
	Bool disk_mode;
	SFVec3f grab_start;
	M4Plane yplane, zplane, xplane;
} CylinderSensorStack;

static void DestroyCylinderSensor(SFNode *node)
{
	CylinderSensorStack *st = (CylinderSensorStack *) Node_GetPrivate(node);
	R3D_SensorDeleted(st->compositor, &st->hdl);
	free(st);
}

static Bool cs_is_enabled(SFNode *n)
{
	M_CylinderSensor *cs = (M_CylinderSensor *)n;
	return (cs->enabled || cs->isActive);
}

static void OnCylinderSensor(SensorHandler *sh, Bool is_over, u32 eventType, RayHitInfo *hit_info)
{
	M_CylinderSensor *cs = (M_CylinderSensor *)sh->owner;
	CylinderSensorStack *st = (CylinderSensorStack *) Node_GetPrivate(sh->owner);

	if (cs->isActive && (!cs->enabled || (eventType==M4E_LEFTUP)) ) {
		if (cs->autoOffset) {
			cs->offset = cs->rotation_changed.q;
			Node_OnEventOutSTR(sh->owner, "offset");
		}
		cs->isActive = 0;
		Node_OnEventOutSTR(sh->owner, "isActive");
		R3D_SetGrabbed(st->compositor, 0);
	}
	else if ((eventType==M4E_LEFTDOWN) && !cs->isActive) {
		M4Ray r;
		SFVec3f yaxis;
		Fixed acute, reva;
		SFVec3f bearing;

		m4_mx_copy(st->init_matrix, hit_info->world_to_local);
		/*get initial angle & check disk mode*/
		r = hit_info->world_ray;
		m4_vec_add(r.dir, r.orig, r.dir);
		m4_mx_apply_vec(&hit_info->world_to_local, &r.orig);
		m4_mx_apply_vec(&hit_info->world_to_local, &r.dir);
		m4_vec_diff(bearing, r.orig, r.dir);
		m4_vec_norm(&bearing);
		yaxis.x = yaxis.z = 0;
		yaxis.y = FIX_ONE;
		acute = m4_vec_dot(bearing, yaxis);
		if (acute < -FIX_ONE) acute = -FIX_ONE; 
		else if (acute > FIX_ONE) acute = FIX_ONE;
		acute = m4_acos(acute);
		reva = ABS(M4_PI - acute);
		if (reva<acute) acute = reva;
		st->disk_mode = (acute < cs->diskAngle) ? 1 : 0;

		st->grab_start = hit_info->local_point;
		/*cos we're lazy*/
		st->yplane.d = 0; st->yplane.normal.x = st->yplane.normal.z = st->yplane.normal.y = 0;
		st->zplane = st->xplane = st->yplane;
		st->xplane.normal.x = FIX_ONE;
		st->yplane.normal.y = FIX_ONE;
		st->zplane.normal.z = FIX_ONE;

		cs->rotation_changed.x = 0;
		cs->rotation_changed.y = FIX_ONE;
		cs->rotation_changed.z = 0;

		cs->isActive = 1;
		Node_OnEventOutSTR(sh->owner, "isActive");
		R3D_SetGrabbed(st->compositor, 1);
	}
	else if (cs->isActive) {
		M4Ray r;
		Fixed radius, rot;
		SFVec3f dir1, dir2, cx;

		if (is_over) {
			cs->trackPoint_changed = hit_info->local_point;
			Node_OnEventOutSTR(sh->owner, "trackPoint_changed");
		} else {
			M4Plane project_to;
			r = hit_info->world_ray;
			m4_mx_apply_ray(&st->init_matrix, &r);

			/*no intersection, use intersection with "main" fronting plane*/
			if ( ABS(r.dir.z) > ABS(r.dir.y)) {
				if (ABS(r.dir.x) > ABS(r.dir.x)) project_to = st->xplane;
				else project_to = st->zplane;
			} else project_to = st->yplane;
			if (!m4_plane_intersect_line(&project_to, &r.orig, &r.dir, &hit_info->local_point)) return;
		}

  		dir1.x = hit_info->local_point.x; dir1.y = 0; dir1.z = hit_info->local_point.z;
		if (st->disk_mode) {
			radius = FIX_ONE;
		} else {
			radius = m4_vec_len(dir1);
		}
		m4_vec_norm(&dir1);
       	dir2.x = st->grab_start.x; dir2.y = 0; dir2.z = st->grab_start.z;
		m4_vec_norm(&dir2);
		cx = m4_vec_cross(dir2, dir1);
		m4_vec_norm(&cx);
		if (m4_vec_len(cx)<M4_EPSILON_FLOAT) return;
		rot = m4_mulfix(radius, m4_acos(m4_vec_dot(dir2, dir1)) );
		if (fabs(cx.y + FIX_ONE) < FIX_EPSILON) rot = -rot;
		if (cs->autoOffset) rot += cs->offset;

		if (cs->minAngle < cs->maxAngle) {
			if (rot < cs->minAngle) rot = cs->minAngle;
			else if (rot > cs->maxAngle) rot = cs->maxAngle;
		}
		cs->rotation_changed.q = rot;
		Node_OnEventOutSTR(sh->owner, "rotation_changed");
	}
}

SensorHandler *r3d_cs_get_handler(SFNode *n)
{
	CylinderSensorStack *st = (CylinderSensorStack  *)Node_GetPrivate(n);
	return &st->hdl;
}

void R3D_InitCylinderSensor(Render3D *sr, SFNode *node)
{
	CylinderSensorStack *st = malloc(sizeof(CylinderSensorStack));
	memset(st, 0, sizeof(CylinderSensorStack));
	st->hdl.IsEnabled = cs_is_enabled;
	st->hdl.OnUserEvent = OnCylinderSensor;
	st->hdl.owner = node;
	st->compositor = sr->compositor;
	st->compositor->interaction_sensors++;
	Node_SetPrivate(node, st);
	Node_SetPreDestroyFunction(node, DestroyCylinderSensor);
}


typedef struct 
{
	SensorHandler hdl;
	struct scene_renderer *compositor;
	Fixed radius;
	/*center in world coords */
	SFVec3f grab_vec, center;
} SphereSensorStack;

static void DestroySphereSensor(SFNode *node)
{
	SphereSensorStack *st = (SphereSensorStack *) Node_GetPrivate(node);
	R3D_SensorDeleted(st->compositor, &st->hdl);
	free(st);
}

static Bool sphere_is_enabled(SFNode *n)
{
	M_SphereSensor *ss = (M_SphereSensor *)n;
	return (ss->enabled || ss->isActive);
}

static void OnSphereSensor(SensorHandler *sh, Bool is_over, u32 eventType, RayHitInfo *hit_info)
{
	M_SphereSensor *sphere = (M_SphereSensor *)sh->owner;
	SphereSensorStack *st = (SphereSensorStack *) Node_GetPrivate(sh->owner);

	if (sphere->isActive && (!sphere->enabled || (eventType==M4E_LEFTUP)) ) {
		if (sphere->autoOffset) {
			sphere->offset = sphere->rotation_changed;
			Node_OnEventOutSTR(sh->owner, "offset");
		}
		sphere->isActive = 0;
		Node_OnEventOutSTR(sh->owner, "isActive");
		R3D_SetGrabbed(st->compositor, 0);
	}
	else if ((eventType==M4E_LEFTDOWN) && !sphere->isActive) {
		st->center.x = st->center.y = st->center.z = 0;
		m4_mx_apply_vec(&hit_info->local_to_world, &st->center);
		st->radius = m4_vec_len(hit_info->local_point);
		if (!st->radius) st->radius = FIX_ONE;
		st->grab_vec = m4_vec_scale(hit_info->local_point, m4_divfix(FIX_ONE, st->radius));

		sphere->isActive = 1;
		Node_OnEventOutSTR(sh->owner, "isActive");
		R3D_SetGrabbed(st->compositor, 1);
	}
	else if (sphere->isActive) {
		SFVec3f vec, axis;
		SFVec4f q1, q2;
		SFRotation r;
		Fixed cl;
		if (is_over) {
			sphere->trackPoint_changed = hit_info->local_point;
			Node_OnEventOutSTR(sh->owner, "trackPoint_changed");
		} else {
			M4Ray r;
			r = hit_info->world_ray;
			m4_mx_apply_ray(&hit_info->world_to_local, &r);
			if (!m4_ray_hit_sphere(&r, NULL, st->radius, &hit_info->local_point)) {
				vec.x = vec.y = vec.z = 0;
				/*doesn't work properly...*/
				hit_info->local_point = m4_closest_point_to_line(r.orig, r.dir, vec);
			}
		}

		vec = m4_vec_scale(hit_info->local_point, m4_divfix(FIX_ONE, st->radius));
		axis = m4_vec_cross(st->grab_vec, vec);
		cl = m4_vec_len(axis);

		if (cl < -FIX_ONE) cl = -FIX_ONE;
		else if (cl > FIX_ONE) cl = FIX_ONE;
		r.q = m4_asin(cl);
		if (m4_vec_dot(st->grab_vec, vec) < 0) r.q += M4_PI / 2;

		m4_vec_norm(&axis);
		r.x = axis.x; r.y = axis.y; r.z = axis.z;
		q1 = m4_quat_from_rotation(r);
		if (sphere->autoOffset) {
			q2 = m4_quat_from_rotation(sphere->offset);
			q1 = m4_quat_multiply(&q1, &q2);
		}
		sphere->rotation_changed = m4_quat_to_rotation(&q1);
		Node_OnEventOutSTR(sh->owner, "rotation_changed");
	}
}

SensorHandler *r3d_sphere_get_handler(SFNode *n)
{
	SphereSensorStack *st = (SphereSensorStack *)Node_GetPrivate(n);
	return &st->hdl;
}

void R3D_InitSphereSensor(Render3D *sr, SFNode *node)
{
	SphereSensorStack *st = malloc(sizeof(SphereSensorStack));
	memset(st, 0, sizeof(SphereSensorStack));
	st->hdl.IsEnabled = sphere_is_enabled;
	st->hdl.OnUserEvent = OnSphereSensor;
	st->hdl.owner = node;
	st->compositor = sr->compositor;
	st->compositor->interaction_sensors++;
	Node_SetPrivate(node, st);
	Node_SetPreDestroyFunction(node, DestroySphereSensor);
}

void RenderVisibilitySensor(SFNode *node, void *rs)
{
	RenderEffect3D *eff = (RenderEffect3D *)rs;
	M_VisibilitySensor *vs = (M_VisibilitySensor *)node;

	if (!vs->enabled) return;

	if (eff->traversing_mode==TRAVERSE_GET_BOUNDS) {
		/*work with twice bigger bbox to get sure we're notify when culled out*/
		m4_vec_add(eff->bbox.max_edge, vs->center, vs->size);
		m4_vec_diff(eff->bbox.min_edge, vs->center, vs->size);
		m4_bbox_refresh(&eff->bbox);

	} else if (eff->traversing_mode==TRAVERSE_SORT) {
		Bool visible;
		u32 cull_flag;
		M4BBox bbox;
		SFVec3f s;
		s = m4_vec_scale(vs->size, FIX_ONE/2);
		/*cull with normal bbox*/
		m4_vec_add(bbox.max_edge, vs->center, s);
		m4_vec_diff(bbox.min_edge, vs->center, s);
		m4_bbox_refresh(&bbox);
		cull_flag = eff->cull_flag;
		eff->cull_flag = CULL_INTERSECTS;
		visible = node_cull(eff, &bbox, 0);
		eff->cull_flag = cull_flag;

		if (visible && !vs->isActive) {
			vs->isActive = 1;
			Node_OnEventOutSTR(node, "isActive");
			vs->enterTime = Node_GetSceneTime(node);
			Node_OnEventOutSTR(node, "enterTime");
		}
		else if (!visible && vs->isActive) {
			vs->isActive = 0;
			Node_OnEventOutSTR(node, "isActive");
			vs->exitTime = Node_GetSceneTime(node);
			Node_OnEventOutSTR(node, "exitTime");
		}
	}
}

void R3D_InitVisibilitySensor(Render3D *sr, SFNode *node)
{
	Node_SetRenderFunction(node, RenderVisibilitySensor);
}
