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

#ifndef GPAC_DISABLE_COMPOSITOR

#include "offscreen_cache.h"
#include "mpeg4_grouping.h"

#include <gpac/modules/hardcoded_proto.h>
#include "texturing.h"


#define CHECK_FIELD(__name, __index, __type) \
	if (gf_node_get_field(node, __index, &field) != GF_OK) {\
		GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[HardcodedProtos] Cannot get field index %d for proto %s\n", __index, __name));\
		return GF_FALSE; \
	}\
	if (field.fieldType != __type) {\
		GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[HardcodedProtos] %s field idx %d (%s) is not of type %s\n", __name, field.fieldIndex, field.name, gf_sg_vrml_get_field_type_name(__type) ));\
		return GF_FALSE;\
	}


#ifndef GPAC_DISABLE_VRML

#ifndef GPAC_DISABLE_3D


/*PathExtrusion hardcoded proto*/

typedef struct
{
	GF_Node *geometry;
	MFVec3f *spine;
	Bool beginCap;
	Bool endCap;
	Fixed creaseAngle;
	MFRotation *orientation;
	MFVec2f *scale;
	Bool txAlongSpine;
} PathExtrusion;


static Bool PathExtrusion_GetNode(GF_Node *node, PathExtrusion *path_ext)
{
	GF_FieldInfo field;
	memset(path_ext, 0, sizeof(PathExtrusion));
	
	CHECK_FIELD("PathExtrusion", 0, GF_SG_VRML_SFNODE);
	path_ext->geometry = * (GF_Node **) field.far_ptr;
	
	CHECK_FIELD("PathExtrusion", 1, GF_SG_VRML_MFVEC3F);
	path_ext->spine = (MFVec3f *) field.far_ptr;

	CHECK_FIELD("PathExtrusion", 2, GF_SG_VRML_SFBOOL);
	path_ext->beginCap = *(SFBool *) field.far_ptr;
	
	CHECK_FIELD("PathExtrusion", 3, GF_SG_VRML_SFBOOL);
	path_ext->endCap = *(SFBool *) field.far_ptr;
	
	CHECK_FIELD("PathExtrusion", 4, GF_SG_VRML_SFFLOAT);
	path_ext->creaseAngle = *(SFFloat *) field.far_ptr;
	
	CHECK_FIELD("PathExtrusion", 5, GF_SG_VRML_MFROTATION);
	path_ext->orientation = (MFRotation *) field.far_ptr;
	
	CHECK_FIELD("PathExtrusion", 6, GF_SG_VRML_MFVEC2F);
	path_ext->scale = (MFVec2f *) field.far_ptr;
	
	CHECK_FIELD("PathExtrusion", 7, GF_SG_VRML_SFBOOL);
	path_ext->txAlongSpine = *(SFBool *) field.far_ptr;
	return GF_TRUE;
}

static void TraversePathExtrusion(GF_Node *node, void *rs, Bool is_destroy)
{
	PathExtrusion path_ext;
	GF_TraverseState *tr_state = (GF_TraverseState *)rs;
	Drawable3D *stack = (Drawable3D *)gf_node_get_private(node);

	if (is_destroy) {
		drawable_3d_del(node);
		return;
	}
	if (!PathExtrusion_GetNode(node, &path_ext)) return;
	if (!path_ext.geometry) return;


	if (gf_node_dirty_get(node)) {
		Drawable *stack_2d;
		u32 mode = tr_state->traversing_mode;
		tr_state->traversing_mode = TRAVERSE_GET_BOUNDS;
		gf_node_traverse(path_ext.geometry, tr_state);
		tr_state->traversing_mode = mode;

		gf_node_dirty_clear(node, 0);

		switch (gf_node_get_tag(path_ext.geometry) ) {
		case TAG_MPEG4_Circle:
		case TAG_MPEG4_Ellipse:
		case TAG_MPEG4_Rectangle:
		case TAG_MPEG4_Curve2D:
		case TAG_MPEG4_XCurve2D:
		case TAG_MPEG4_IndexedFaceSet2D:
		case TAG_MPEG4_IndexedLineSet2D:
			stack_2d = (Drawable*)gf_node_get_private(path_ext.geometry);
			if (!stack_2d) return;
			mesh_extrude_path(stack->mesh, stack_2d->path, path_ext.spine, path_ext.creaseAngle, path_ext.beginCap, path_ext.endCap, path_ext.orientation, path_ext.scale, path_ext.txAlongSpine);
			break;
		case TAG_MPEG4_Text:
			compositor_extrude_text(path_ext.geometry, tr_state, stack->mesh, path_ext.spine, path_ext.creaseAngle, path_ext.beginCap, path_ext.endCap, path_ext.orientation, path_ext.scale, path_ext.txAlongSpine);
			break;
		}
	}

	if (tr_state->traversing_mode==TRAVERSE_DRAW_3D) {
		visual_3d_draw(tr_state, stack->mesh);
	} else if (tr_state->traversing_mode==TRAVERSE_GET_BOUNDS) {
		tr_state->bbox = stack->mesh->bounds;
	}
}

static void compositor_init_path_extrusion(GF_Compositor *compositor, GF_Node *node)
{
	drawable_3d_new(node);
	gf_node_set_callback_function(node, TraversePathExtrusion);
}


/*PlanarExtrusion hardcoded proto*/
typedef struct
{
	GF_Node *geometry;
	GF_Node *spine;
	Bool beginCap;
	Bool endCap;
	Fixed creaseAngle;
	MFFloat *orientationKeys;
	MFRotation *orientation;
	MFFloat *scaleKeys;
	MFVec2f *scale;
	Bool txAlongSpine;
} PlanarExtrusion;

static Bool PlanarExtrusion_GetNode(GF_Node *node, PlanarExtrusion *path_ext)
{
	GF_FieldInfo field;
	memset(path_ext, 0, sizeof(PathExtrusion));

	CHECK_FIELD("PlanarExtrusion", 0, GF_SG_VRML_SFNODE);
	path_ext->geometry = * (GF_Node **) field.far_ptr;
	
	CHECK_FIELD("PlanarExtrusion", 1, GF_SG_VRML_SFNODE);
	path_ext->spine = * (GF_Node **) field.far_ptr;
	
	CHECK_FIELD("PlanarExtrusion", 2, GF_SG_VRML_SFBOOL);
	path_ext->beginCap = *(SFBool *) field.far_ptr;
	
	CHECK_FIELD("PlanarExtrusion", 3, GF_SG_VRML_SFBOOL);
	path_ext->endCap = *(SFBool *) field.far_ptr;
	
	CHECK_FIELD("PlanarExtrusion", 4, GF_SG_VRML_SFFLOAT);
	path_ext->creaseAngle = *(SFFloat *) field.far_ptr;
	
	CHECK_FIELD("PlanarExtrusion", 5, GF_SG_VRML_MFFLOAT);
	path_ext->orientationKeys = (MFFloat *) field.far_ptr;
	
	CHECK_FIELD("PlanarExtrusion", 6, GF_SG_VRML_MFROTATION);
	path_ext->orientation = (MFRotation *) field.far_ptr;
	
	CHECK_FIELD("PlanarExtrusion", 7, GF_SG_VRML_MFFLOAT);
	path_ext->scaleKeys = (MFFloat *) field.far_ptr;
	
	CHECK_FIELD("PlanarExtrusion", 8, GF_SG_VRML_MFVEC2F);
	path_ext->scale = (MFVec2f *) field.far_ptr;
	
	CHECK_FIELD("PlanarExtrusion", 9, GF_SG_VRML_SFBOOL);
	path_ext->txAlongSpine = *(SFBool *) field.far_ptr;

	return GF_TRUE;
}

static void TraversePlanarExtrusion(GF_Node *node, void *rs, Bool is_destroy)
{
	PlanarExtrusion plane_ext;
	GF_TraverseState *tr_state = (GF_TraverseState *)rs;
	Drawable3D *stack = (Drawable3D *)gf_node_get_private(node);

	if (is_destroy) {
		drawable_3d_del(node);
		return;
	}

	if (!PlanarExtrusion_GetNode(node, &plane_ext)) return;
	if (!plane_ext.geometry || !plane_ext.spine) return;


	if (gf_node_dirty_get(node)) {
		Drawable *stack_2d;
		u32 i, j, k;
		MFVec3f spine_vec;
		SFVec3f d;
		Fixed spine_len;
		GF_Rect bounds;
		u32 cur, nb_pts;
		u32 mode = tr_state->traversing_mode;
		GF_Path *geo, *spine;
		geo = spine = NULL;

		tr_state->traversing_mode = TRAVERSE_GET_BOUNDS;
		gf_node_traverse(plane_ext.geometry, tr_state);
		gf_node_traverse(plane_ext.spine, tr_state);
		tr_state->traversing_mode = mode;
		gf_node_dirty_clear(node, 0);

		switch (gf_node_get_tag(plane_ext.geometry) ) {
		case TAG_MPEG4_Circle:
		case TAG_MPEG4_Ellipse:
		case TAG_MPEG4_Rectangle:
		case TAG_MPEG4_Curve2D:
		case TAG_MPEG4_XCurve2D:
		case TAG_MPEG4_IndexedFaceSet2D:
		case TAG_MPEG4_IndexedLineSet2D:
			stack_2d = (Drawable*)gf_node_get_private(plane_ext.geometry);
			if (stack_2d) geo = stack_2d->path;
			break;
		default:
			return;
		}
		switch (gf_node_get_tag(plane_ext.spine) ) {
		case TAG_MPEG4_Circle:
		case TAG_MPEG4_Ellipse:
		case TAG_MPEG4_Rectangle:
		case TAG_MPEG4_Curve2D:
		case TAG_MPEG4_XCurve2D:
		case TAG_MPEG4_IndexedFaceSet2D:
		case TAG_MPEG4_IndexedLineSet2D:
			stack_2d = (Drawable*)gf_node_get_private(plane_ext.spine);
			if (stack_2d) spine = stack_2d->path;
			break;
		default:
			return;
		}
		if (!geo || !spine) return;

		mesh_reset(stack->mesh);
		gf_path_flatten(spine);
		gf_path_get_bounds(spine, &bounds);
		gf_path_flatten(geo);
		gf_path_get_bounds(geo, &bounds);

		cur = 0;
		for (i=0; i<spine->n_contours; i++) {
			nb_pts = 1 + spine->contours[i] - cur;
			spine_vec.vals = NULL;
			gf_sg_vrml_mf_alloc(&spine_vec, GF_SG_VRML_MFVEC3F, nb_pts);
			spine_len = 0;
			for (j=cur; j<nb_pts; j++) {
				spine_vec.vals[j].x = spine->points[j].x;
				spine_vec.vals[j].y = spine->points[j].y;
				spine_vec.vals[j].z = 0;
				if (j) {
					gf_vec_diff(d, spine_vec.vals[j], spine_vec.vals[j-1]);
					spine_len += gf_vec_len(d);
				}
			}
			cur += nb_pts;
			if (!plane_ext.orientation->count && !plane_ext.scale->count) {
				mesh_extrude_path_ext(stack->mesh, geo, &spine_vec, plane_ext.creaseAngle,
				                      bounds.x, bounds.y-bounds.height, bounds.width, bounds.height,
				                      plane_ext.beginCap, plane_ext.endCap, NULL, NULL, plane_ext.txAlongSpine);
			}
			/*interpolate orientation and scale along subpath line*/
			else {
				MFRotation ori;
				MFVec2f scale;
				Fixed cur_len, frac;

				ori.vals = NULL;
				gf_sg_vrml_mf_alloc(&ori, GF_SG_VRML_MFROTATION, nb_pts);
				scale.vals = NULL;
				gf_sg_vrml_mf_alloc(&scale, GF_SG_VRML_MFVEC2F, nb_pts);
				cur_len = 0;
				if (!plane_ext.orientation->count) ori.vals[0].y = FIX_ONE;
				if (!plane_ext.scale->count) scale.vals[0].x = scale.vals[0].y = FIX_ONE;
				for (j=0; j<nb_pts; j++) {
					if (j) {
						gf_vec_diff(d, spine_vec.vals[j], spine_vec.vals[j-1]);
						cur_len += gf_vec_len(d);
						ori.vals[j] = ori.vals[j-1];
						scale.vals[j] = scale.vals[j-1];
					}

					if (plane_ext.orientation->count && (plane_ext.orientation->count == plane_ext.orientationKeys->count)) {

						frac = gf_divfix(cur_len , spine_len);
						if (frac < plane_ext.orientationKeys->vals[0]) ori.vals[j] = plane_ext.orientation->vals[0];
						else if (frac >= plane_ext.orientationKeys->vals[plane_ext.orientationKeys->count-1]) ori.vals[j] = plane_ext.orientation->vals[plane_ext.orientationKeys->count-1];
						else {
							for (k=1; k<plane_ext.orientationKeys->count; k++) {
								Fixed kDiff = plane_ext.orientationKeys->vals[k] - plane_ext.orientationKeys->vals[k-1];
								if (!kDiff) continue;
								if (frac < plane_ext.orientationKeys->vals[k-1]) continue;
								if (frac > plane_ext.orientationKeys->vals[k]) continue;
								frac = gf_divfix(frac - plane_ext.orientationKeys->vals[k-1], kDiff);
								break;
							}
							ori.vals[j] = gf_sg_sfrotation_interpolate(plane_ext.orientation->vals[k-1], plane_ext.orientation->vals[k], frac);
						}
					}

					if (plane_ext.scale->count == plane_ext.scaleKeys->count) {
						frac = gf_divfix(cur_len , spine_len);
						if (frac <= plane_ext.scaleKeys->vals[0]) scale.vals[j] = plane_ext.scale->vals[0];
						else if (frac >= plane_ext.scaleKeys->vals[plane_ext.scaleKeys->count-1]) scale.vals[j] = plane_ext.scale->vals[plane_ext.scale->count-1];
						else {
							for (k=1; k<plane_ext.scaleKeys->count; k++) {
								Fixed kDiff = plane_ext.scaleKeys->vals[k] - plane_ext.scaleKeys->vals[k-1];
								if (!kDiff) continue;
								if (frac < plane_ext.scaleKeys->vals[k-1]) continue;
								if (frac > plane_ext.scaleKeys->vals[k]) continue;
								frac = gf_divfix(frac - plane_ext.scaleKeys->vals[k-1], kDiff);
								break;
							}
							scale.vals[j].x = gf_mulfix(plane_ext.scale->vals[k].x - plane_ext.scale->vals[k-1].x, frac) + plane_ext.scale->vals[k-1].x;
							scale.vals[j].y = gf_mulfix(plane_ext.scale->vals[k].y - plane_ext.scale->vals[k-1].y, frac) + plane_ext.scale->vals[k-1].y;
						}
					}
				}

				mesh_extrude_path_ext(stack->mesh, geo, &spine_vec, plane_ext.creaseAngle,
				                      bounds.x, bounds.y-bounds.height, bounds.width, bounds.height,
				                      plane_ext.beginCap, plane_ext.endCap, &ori, &scale, plane_ext.txAlongSpine);

				gf_sg_vrml_mf_reset(&ori, GF_SG_VRML_MFROTATION);
				gf_sg_vrml_mf_reset(&scale, GF_SG_VRML_MFVEC2F);
			}

			gf_sg_vrml_mf_reset(&spine_vec, GF_SG_VRML_MFVEC3F);
		}
		mesh_update_bounds(stack->mesh);
		gf_mesh_build_aabbtree(stack->mesh);
	}

	if (tr_state->traversing_mode==TRAVERSE_DRAW_3D) {
		visual_3d_draw(tr_state, stack->mesh);
	} else if (tr_state->traversing_mode==TRAVERSE_GET_BOUNDS) {
		tr_state->bbox = stack->mesh->bounds;
	}
}

void compositor_init_planar_extrusion(GF_Compositor *compositor, GF_Node *node)
{
	drawable_3d_new(node);
	gf_node_set_callback_function(node, TraversePlanarExtrusion);
}

/*PlaneClipper hardcoded proto*/
typedef struct
{
	BASE_NODE
	CHILDREN

	GF_Plane plane;
} PlaneClipper;

typedef struct
{
	GROUPING_NODE_STACK_3D
	PlaneClipper pc;
} PlaneClipperStack;

static Bool PlaneClipper_GetNode(GF_Node *node, PlaneClipper *pc)
{
	GF_FieldInfo field;
	memset(pc, 0, sizeof(PlaneClipper));
	pc->sgprivate = node->sgprivate;

	CHECK_FIELD("PlaneClipper", 0, GF_SG_VRML_SFVEC3F);
	pc->plane.normal = * (SFVec3f *) field.far_ptr;

	CHECK_FIELD("PlaneClipper", 1, GF_SG_VRML_SFFLOAT);
	pc->plane.d = * (SFFloat *) field.far_ptr;
	
	CHECK_FIELD("PlaneClipper", 2, GF_SG_VRML_MFNODE);
	pc->children = *(GF_ChildNodeItem **) field.far_ptr;
	return GF_TRUE;
}


static void TraversePlaneClipper(GF_Node *node, void *rs, Bool is_destroy)
{
	PlaneClipperStack *stack = (PlaneClipperStack *)gf_node_get_private(node);
	GF_TraverseState *tr_state = (GF_TraverseState *) rs;

	if (is_destroy) {
		group_3d_delete(node);
		return;
	}

	if (gf_node_dirty_get(node)) {
		PlaneClipper_GetNode(node, &stack->pc);
		gf_node_dirty_clear(node, GF_SG_NODE_DIRTY);
	}

	if (tr_state->num_clip_planes==MAX_USER_CLIP_PLANES) {
		group_3d_traverse((GF_Node*)&stack->pc, (GroupingNode*)stack, tr_state);
		return;
	}

	if (tr_state->traversing_mode == TRAVERSE_SORT) {
		GF_Matrix mx;
		gf_mx_copy(mx, tr_state->model_matrix);
		visual_3d_set_clip_plane(tr_state->visual, stack->pc.plane, &mx, GF_FALSE);
		tr_state->num_clip_planes++;

		group_3d_traverse((GF_Node*)&stack->pc, (GroupingNode*)stack, tr_state);
		visual_3d_reset_clip_plane(tr_state->visual);
		tr_state->num_clip_planes--;
	} else {
		tr_state->clip_planes[tr_state->num_clip_planes] = stack->pc.plane;
		gf_mx_apply_plane(&tr_state->model_matrix, &tr_state->clip_planes[tr_state->num_clip_planes]);
		tr_state->num_clip_planes++;

		group_3d_traverse((GF_Node*)&stack->pc, (GroupingNode*)stack, tr_state);

		tr_state->num_clip_planes--;
	}

}

void compositor_init_plane_clipper(GF_Compositor *compositor, GF_Node *node)
{
	PlaneClipper pc;
	if (PlaneClipper_GetNode(node, &pc)) {
		PlaneClipperStack *stack;
		GF_SAFEALLOC(stack, PlaneClipperStack);
		if (!stack) {
			GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to allocate plane clipper stack\n"));
			return;
		}
		//SetupGroupingNode(stack, compositor->compositor, node, & pc.children);
		gf_node_set_private(node, stack);
		gf_node_set_callback_function(node, TraversePlaneClipper);
		/*we're a grouping node, force bounds rebuild as soon as loaded*/
		gf_node_dirty_set(node, GF_SG_CHILD_DIRTY, GF_FALSE);

		stack->pc = pc;
		gf_node_proto_set_grouping(node);
	}
}

#endif


/*OffscreenGroup hardcoded proto*/
typedef struct
{
	BASE_NODE
	CHILDREN

	s32 offscreen;
	Fixed opacity;
} OffscreenGroup;

typedef struct
{
	GROUPING_MPEG4_STACK_2D

#ifndef GF_SR_USE_VIDEO_CACHE
	struct _group_cache *cache;
#endif

	OffscreenGroup og;
	Bool detached;
} OffscreenGroupStack;

static Bool OffscreenGroup_GetNode(GF_Node *node, OffscreenGroup *og)
{
	GF_FieldInfo field;
	memset(og, 0, sizeof(OffscreenGroup));
	og->sgprivate = node->sgprivate;

	CHECK_FIELD("OffscreenGroup", 0, GF_SG_VRML_MFNODE);
	og->children = *(GF_ChildNodeItem **) field.far_ptr;

	CHECK_FIELD("OffscreenGroup", 1, GF_SG_VRML_SFINT32);
	og->offscreen = * (SFInt32 *) field.far_ptr;

	CHECK_FIELD("OffscreenGroup", 2, GF_SG_VRML_SFFLOAT);
	og->opacity = * (SFFloat *) field.far_ptr;
	
	return GF_TRUE;
}


static void TraverseOffscreenGroup(GF_Node *node, void *rs, Bool is_destroy)
{
	OffscreenGroupStack *stack = (OffscreenGroupStack *)gf_node_get_private(node);
	GF_TraverseState *tr_state = (GF_TraverseState *) rs;

	if (is_destroy) {
		if (stack->cache) group_cache_del(stack->cache);
		gf_free(stack);
		return;
	}

	if (tr_state->traversing_mode==TRAVERSE_SORT) {
		if (!stack->detached && (gf_node_dirty_get(node) & GF_SG_NODE_DIRTY)) {
			OffscreenGroup_GetNode(node, &stack->og);

			if (stack->og.offscreen) {
				stack->flags |= GROUP_IS_CACHED | GROUP_PERMANENT_CACHE;
				if (!stack->cache) {
					stack->cache = group_cache_new(tr_state->visual->compositor, (GF_Node*)&stack->og);
				}
				stack->cache->opacity = stack->og.opacity;
				stack->cache->drawable->flags |= DRAWABLE_HAS_CHANGED;
			} else {
				if (stack->cache) group_cache_del(stack->cache);
				stack->cache = NULL;
				stack->flags &= ~(GROUP_IS_CACHED|GROUP_PERMANENT_CACHE);
			}
			gf_node_dirty_clear(node, GF_SG_NODE_DIRTY);
			/*flag is not set for PROTO*/
			gf_node_dirty_set(node, GF_SG_CHILD_DIRTY, GF_FALSE);
		}
		if (stack->cache) {
			if (stack->detached)
				gf_node_dirty_clear(node, GF_SG_CHILD_DIRTY);

			tr_state->subscene_not_over = 0;
			group_cache_traverse((GF_Node *)&stack->og, stack->cache, tr_state, stack->cache->force_recompute, GF_TRUE, stack->detached ? GF_TRUE : GF_FALSE);

			if (gf_node_dirty_get(node)) {
				gf_node_dirty_clear(node, GF_SG_CHILD_DIRTY);
			} else if ((stack->og.offscreen==2) && !stack->detached && !tr_state->subscene_not_over && stack->cache->txh.width && stack->cache->txh.height) {
				GF_FieldInfo field;
				if (gf_node_get_field(node, 0, &field) == GF_OK) {
					gf_node_unregister_children(node, *(GF_ChildNodeItem **) field.far_ptr);
					*(GF_ChildNodeItem **) field.far_ptr = NULL;
					stack->detached = GF_TRUE;
				}
				if (gf_node_get_field(node, 3, &field) == GF_OK) {
					*(SFBool *) field.far_ptr = 1;
					//gf_node_event_out(node, 3);
				}
			}
		} else {
			group_2d_traverse((GF_Node *)&stack->og, (GroupingNode2D*)stack, tr_state);
		}
	}
	/*draw mode*/
	else if (stack->cache && (tr_state->traversing_mode == TRAVERSE_DRAW_2D)) {
		/*draw it*/
		group_cache_draw(stack->cache, tr_state);
		gf_node_dirty_clear(node, GF_SG_CHILD_DIRTY);
	} else if (!stack->detached) {
		group_2d_traverse((GF_Node *)&stack->og, (GroupingNode2D*)stack, tr_state);
	} else {
		if (tr_state->traversing_mode == TRAVERSE_GET_BOUNDS) {
			tr_state->bounds = stack->bounds;
		}
		else if (stack->cache && (tr_state->traversing_mode == TRAVERSE_PICK)) {
			vrml_drawable_pick(stack->cache->drawable, tr_state);
		}
	}
}

void compositor_init_offscreen_group(GF_Compositor *compositor, GF_Node *node)
{
	OffscreenGroup og;
	if (OffscreenGroup_GetNode(node, &og)) {
		OffscreenGroupStack *stack;
		GF_SAFEALLOC(stack, OffscreenGroupStack);
		if (!stack) {
			GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to allocate offscreen group stack\n"));
			return;
		}
		gf_node_set_private(node, stack);
		gf_node_set_callback_function(node, TraverseOffscreenGroup);
		stack->og = og;
		if (og.offscreen) stack->flags |= GROUP_IS_CACHED;
		gf_node_proto_set_grouping(node);
	}
}


/*DepthGroup hardcoded proto*/
typedef struct
{
	BASE_NODE
	CHILDREN

	Fixed depth_gain, depth_offset;

} DepthGroup;

typedef struct
{
	GROUPING_MPEG4_STACK_2D
	DepthGroup dg;
} DepthGroupStack;

static Bool DepthGroup_GetNode(GF_Node *node, DepthGroup *dg)
{
	GF_FieldInfo field;
	memset(dg, 0, sizeof(DepthGroup));
	dg->sgprivate = node->sgprivate;

	CHECK_FIELD("DepthGroup", 0, GF_SG_VRML_MFNODE);
	dg->children = *(GF_ChildNodeItem **) field.far_ptr;

	CHECK_FIELD("DepthGroup", 1, GF_SG_VRML_SFFLOAT);
	dg->depth_gain = * (SFFloat *) field.far_ptr;

	CHECK_FIELD("DepthGroup", 2, GF_SG_VRML_SFFLOAT);
	dg->depth_offset = * (SFFloat *) field.far_ptr;

	return GF_TRUE;
}


static void TraverseDepthGroup(GF_Node *node, void *rs, Bool is_destroy)
{
#ifdef GF_SR_USE_DEPTH
	Fixed depth_gain, depth_offset;
#endif

	DepthGroupStack *stack = (DepthGroupStack *)gf_node_get_private(node);
	GF_TraverseState *tr_state = (GF_TraverseState *) rs;

	if (is_destroy) {
		gf_free(stack);
		return;
	}

	if (tr_state->traversing_mode==TRAVERSE_SORT) {
		if (gf_node_dirty_get(node) & GF_SG_NODE_DIRTY) {

			gf_node_dirty_clear(node, GF_SG_NODE_DIRTY);
			/*flag is not set for PROTO*/
			gf_node_dirty_set(node, GF_SG_CHILD_DIRTY, GF_FALSE);
		}
	}
	DepthGroup_GetNode(node, &stack->dg);


#ifdef GF_SR_USE_DEPTH
	depth_gain = tr_state->depth_gain;
	depth_offset = tr_state->depth_offset;

	// new offset is multiplied by parent gain and added to parent offset
	tr_state->depth_offset = gf_mulfix(stack->dg.depth_offset, tr_state->depth_gain) + tr_state->depth_offset;

	// gain is multiplied by parent gain
	tr_state->depth_gain = gf_mulfix(tr_state->depth_gain, stack->dg.depth_gain);
#endif

#ifndef GPAC_DISABLE_3D
	if (tr_state->visual->type_3d) {
		GF_Matrix mx_bckup, mx;

		gf_mx_copy(mx_bckup, tr_state->model_matrix);
		gf_mx_init(mx);
		mx.m[14] = gf_mulfix(stack->dg.depth_offset, tr_state->visual->compositor->depth_gl_scale);
		gf_mx_add_matrix(&tr_state->model_matrix, &mx);
		group_2d_traverse((GF_Node *)&stack->dg, (GroupingNode2D*)stack, tr_state);
		gf_mx_copy(tr_state->model_matrix, mx_bckup);

	} else
#endif
	{

		group_2d_traverse((GF_Node *)&stack->dg, (GroupingNode2D*)stack, tr_state);
	}

#ifdef GF_SR_USE_DEPTH
	tr_state->depth_gain = depth_gain;
	tr_state->depth_offset = depth_offset;
#endif
}

void compositor_init_depth_group(GF_Compositor *compositor, GF_Node *node)
{
	DepthGroup dg;
	if (DepthGroup_GetNode(node, &dg)) {
		DepthGroupStack *stack;
		GF_SAFEALLOC(stack, DepthGroupStack);
		if (!stack) {
			GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to allocate depth group stack\n"));
			return;
		}
		gf_node_set_private(node, stack);
		gf_node_set_callback_function(node, TraverseDepthGroup);
		stack->dg = dg;
		gf_node_proto_set_grouping(node);
	} else GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Compositor2D] Unable to initialize depth group  \n"));

}

#ifdef GF_SR_USE_DEPTH
static void TraverseDepthViewPoint(GF_Node *node, void *rs, Bool is_destroy)
{
	if (!is_destroy && gf_node_dirty_get(node)) {
		GF_TraverseState *tr_state = (GF_TraverseState *) rs;
		GF_FieldInfo field;
		gf_node_dirty_clear(node, 0);

		tr_state->visual->depth_vp_position = 0;
		tr_state->visual->depth_vp_range = 0;
#ifndef GPAC_DISABLE_3D
		if (!tr_state->camera) return;
		tr_state->camera->flags |= CAM_IS_DIRTY;
#endif

		if (gf_node_get_field(node, 0, &field) != GF_OK) return;
		if (field.fieldType != GF_SG_VRML_SFBOOL) return;

		if ( *(SFBool *) field.far_ptr) {
			if (gf_node_get_field(node, 1, &field) != GF_OK) return;
			if (field.fieldType != GF_SG_VRML_SFFLOAT) return;
			tr_state->visual->depth_vp_position = *(SFFloat *) field.far_ptr;
			if (gf_node_get_field(node, 2, &field) != GF_OK) return;
			if (field.fieldType != GF_SG_VRML_SFFLOAT) return;
			tr_state->visual->depth_vp_range = *(SFFloat *) field.far_ptr;
		}
#ifndef GPAC_DISABLE_3D
		if (tr_state->layer3d) gf_node_dirty_set(tr_state->layer3d, GF_SG_NODE_DIRTY, GF_FALSE);
#endif
		gf_sc_invalidate(tr_state->visual->compositor, NULL);
	}
}
#endif

static void compositor_init_depth_viewpoint(GF_Compositor *compositor, GF_Node *node)
{
#ifdef GF_SR_USE_DEPTH
	gf_node_set_callback_function(node, TraverseDepthViewPoint);
#endif
}

/*IndexedCurve2D hardcoded proto*/

typedef struct
{
	BASE_NODE

	GF_Node *point;
	Fixed fineness;
	MFInt32 type;
	MFInt32 index;
} IndexedCurve2D;

static Bool IndexedCurve2D_GetNode(GF_Node *node, IndexedCurve2D *ic2d)
{
	GF_FieldInfo field;
	memset(ic2d, 0, sizeof(IndexedCurve2D));

	ic2d->sgprivate = node->sgprivate;

	CHECK_FIELD("IndexedCurve2D", 0, GF_SG_VRML_SFNODE);
	ic2d->point = * (GF_Node **) field.far_ptr;

	CHECK_FIELD("IndexedCurve2D", 1, GF_SG_VRML_SFFLOAT);
	ic2d->fineness = *(SFFloat *) field.far_ptr;

	CHECK_FIELD("IndexedCurve2D", 2, GF_SG_VRML_MFINT32);
	ic2d->type = *(MFInt32 *) field.far_ptr;

	CHECK_FIELD("IndexedCurve2D", 3, GF_SG_VRML_MFINT32);
	ic2d->index = *(MFInt32 *) field.far_ptr;

	return GF_TRUE;
}

void curve2d_check_changes(GF_Node *node, Drawable *stack, GF_TraverseState *tr_state, MFInt32 *idx);

static void TraverseIndexedCurve2D(GF_Node *node, void *rs, Bool is_destroy)
{
	DrawableContext *ctx;
	IndexedCurve2D ic2d;
	GF_TraverseState *tr_state = (GF_TraverseState *)rs;
	Drawable *stack = (Drawable *)gf_node_get_private(node);

	if (is_destroy) {
		drawable_node_del(node);
		return;
	}

	if (gf_node_dirty_get(node)) {
		if (!IndexedCurve2D_GetNode(node, &ic2d)) return;
		//clears dirty flag
		curve2d_check_changes((GF_Node*) &ic2d, stack, tr_state, &ic2d.index);
	}

	switch (tr_state->traversing_mode) {
#ifndef GPAC_DISABLE_3D
	case TRAVERSE_DRAW_3D:
		if (!stack->mesh) {
			stack->mesh = new_mesh();
			mesh_from_path(stack->mesh, stack->path);
		}
		visual_3d_draw_2d(stack, tr_state);
		return;
#endif
	case TRAVERSE_PICK:
		vrml_drawable_pick(stack, tr_state);
		return;
	case TRAVERSE_GET_BOUNDS:
		gf_path_get_bounds(stack->path, &tr_state->bounds);
		return;
	case TRAVERSE_SORT:
#ifndef GPAC_DISABLE_3D
		if (tr_state->visual->type_3d) return;
#endif
		ctx = drawable_init_context_mpeg4(stack, tr_state);
		if (!ctx) return;
		drawable_finalize_sort(ctx, tr_state, NULL);
		return;
	}
}

static void compositor_init_idx_curve2d(GF_Compositor *compositor, GF_Node *node)
{
	drawable_stack_new(compositor, node);
	gf_node_set_callback_function(node, TraverseIndexedCurve2D);
}





/*TransformRef hardcoded proto*/
typedef struct
{
	BASE_NODE
	CHILDREN
} Untransform;

typedef struct
{
	GROUPING_MPEG4_STACK_2D
	Untransform untr;
} UntransformStack;

static Bool Untransform_GetNode(GF_Node *node, Untransform *tr)
{
	GF_FieldInfo field;
	memset(tr, 0, sizeof(Untransform));
	tr->sgprivate = node->sgprivate;

	CHECK_FIELD("Untransform", 0, GF_SG_VRML_MFNODE);
	tr->children = *(GF_ChildNodeItem **) field.far_ptr;

	return GF_TRUE;
}


void TraverseUntransformEx(GF_Node *node, void *rs, GroupingNode2D *grp)
{
	GF_TraverseState *tr_state = (GF_TraverseState *) rs;

#ifndef GPAC_DISABLE_3D
	if (tr_state->visual->type_3d) {
		GF_Matrix mx_model;
		GF_Camera backup_cam;

		if (!tr_state->camera) return;

		gf_mx_copy(mx_model, tr_state->model_matrix);
		gf_mx_init(tr_state->model_matrix);

		memcpy(&backup_cam, tr_state->camera, sizeof(GF_Camera));

		camera_invalidate(tr_state->camera);
		tr_state->camera->is_3D = GF_FALSE;
		tr_state->camera->flags |= CAM_NO_LOOKAT;
		tr_state->camera->end_zoom = FIX_ONE;
		camera_update(tr_state->camera, NULL, GF_TRUE);


		if (tr_state->traversing_mode == TRAVERSE_SORT) {
			visual_3d_set_viewport(tr_state->visual, tr_state->camera->proj_vp);
			visual_3d_projection_matrix_modified(tr_state->visual);

			gf_node_traverse_children(node, tr_state);

			gf_mx_copy(tr_state->model_matrix, mx_model);
			memcpy(tr_state->camera, &backup_cam, sizeof(GF_Camera));

			visual_3d_projection_matrix_modified(tr_state->visual);

			visual_3d_set_viewport(tr_state->visual, tr_state->camera->proj_vp);
		} else if (tr_state->traversing_mode == TRAVERSE_PICK) {
			Fixed prev_dist = tr_state->visual->compositor->hit_square_dist;
			GF_Ray r = tr_state->ray;
			tr_state->ray.orig.x = INT2FIX(tr_state->pick_x);
			tr_state->ray.orig.y = INT2FIX(tr_state->pick_y);
			tr_state->ray.orig.z = 0;
			tr_state->ray.dir.x = 0;
			tr_state->ray.dir.y = 0;
			tr_state->ray.dir.z = -FIX_ONE;
			tr_state->visual->compositor->hit_square_dist=0;

			gf_node_traverse_children(node, tr_state);

			gf_mx_copy(tr_state->model_matrix, mx_model);
			memcpy(tr_state->camera, &backup_cam, sizeof(GF_Camera));
			tr_state->ray = r;

			/*nothing picked, restore previous pick*/
			if (!tr_state->visual->compositor->hit_square_dist)
				tr_state->visual->compositor->hit_square_dist = prev_dist;

		} else {
			gf_node_traverse_children(node, tr_state);

			gf_mx_copy(tr_state->model_matrix, mx_model);
			memcpy(tr_state->camera, &backup_cam, sizeof(GF_Camera));
		}

	} else
#endif
	{
		GF_Matrix2D mx2d_backup;
		gf_mx2d_copy(mx2d_backup, tr_state->transform);
		gf_mx2d_init(tr_state->transform);

		group_2d_traverse(node, grp, tr_state);

		gf_mx2d_copy(tr_state->transform, mx2d_backup);
	}
}
static void TraverseUntransform(GF_Node *node, void *rs, Bool is_destroy)
{
	UntransformStack *stack = (UntransformStack *)gf_node_get_private(node);
	GF_TraverseState *tr_state = (GF_TraverseState *) rs;

	if (is_destroy) {
		gf_free(stack);
		return;
	}
	if (tr_state->traversing_mode==TRAVERSE_SORT) {
		if (gf_node_dirty_get(node)) {
			Untransform_GetNode(node, &stack->untr); /*lets place it below*/
			gf_node_dirty_clear(node, GF_SG_NODE_DIRTY);
		}
	}
	TraverseUntransformEx((GF_Node *)&stack->untr, tr_state, (GroupingNode2D *)stack);
}

void compositor_init_untransform(GF_Compositor *compositor, GF_Node *node)
{
	Untransform tr;
	if (Untransform_GetNode(node, &tr)) {
		UntransformStack *stack;
		GF_SAFEALLOC(stack, UntransformStack);
		if (!stack) {
			GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to allocate untransform stack\n"));
			return;
		}
		gf_node_set_private(node, stack);
		gf_node_set_callback_function(node, TraverseUntransform);
		stack->untr = tr;
		gf_node_proto_set_grouping(node);
	}
}



/*StyleGroup: overrides appearance of all children*/
typedef struct
{
	BASE_NODE
	CHILDREN

	GF_Node *appearance;
} StyleGroup;

typedef struct
{
	GROUPING_MPEG4_STACK_2D
	StyleGroup sg;
} StyleGroupStack;

static Bool StyleGroup_GetNode(GF_Node *node, StyleGroup *sg)
{
	GF_FieldInfo field;
	memset(sg, 0, sizeof(StyleGroup));
	sg->sgprivate = node->sgprivate;

	CHECK_FIELD("StyleGroup", 0, GF_SG_VRML_MFNODE);
	sg->children = *(GF_ChildNodeItem **) field.far_ptr;

	CHECK_FIELD("StyleGroup", 1, GF_SG_VRML_SFNODE);
	sg->appearance = *(GF_Node **)field.far_ptr;

	return GF_TRUE;
}


static void TraverseStyleGroup(GF_Node *node, void *rs, Bool is_destroy)
{
	Bool set = GF_FALSE;
	StyleGroupStack *stack = (StyleGroupStack *)gf_node_get_private(node);
	GF_TraverseState *tr_state = (GF_TraverseState *) rs;

	if (is_destroy) {
		gf_free(stack);
		return;
	}

	if (tr_state->traversing_mode==TRAVERSE_SORT) {
		if (gf_node_dirty_get(node) & GF_SG_NODE_DIRTY) {

			gf_node_dirty_clear(node, GF_SG_NODE_DIRTY);
			/*flag is not set for PROTO*/
			gf_node_dirty_set(node, GF_SG_CHILD_DIRTY, GF_FALSE);
		}
	}
	StyleGroup_GetNode(node, &stack->sg);

	if (!tr_state->override_appearance) {
		set = GF_TRUE;
		tr_state->override_appearance = stack->sg.appearance;
	}
	group_2d_traverse((GF_Node *)&stack->sg, (GroupingNode2D*)stack, tr_state);

	if (set) {
		tr_state->override_appearance = NULL;
	}
}

void compositor_init_style_group(GF_Compositor *compositor, GF_Node *node)
{
	StyleGroup sg;
	if (StyleGroup_GetNode(node, &sg)) {
		StyleGroupStack *stack;
		GF_SAFEALLOC(stack, StyleGroupStack);
		if (!stack) {
			GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to allocate style group stack\n"));
			return;
		}
		gf_node_set_private(node, stack);
		gf_node_set_callback_function(node, TraverseStyleGroup);
		stack->sg = sg;
		gf_node_proto_set_grouping(node);
	} else {
		GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Compositor2D] Unable to initialize style group\n"));
	}
}



/*TestSensor: tests eventIn/eventOuts for hardcoded proto*/
typedef struct
{
	BASE_NODE

	Bool onTrigger;
	Fixed value;
} TestSensor;

typedef struct
{
	TestSensor ts;
} TestSensorStack;

static Bool TestSensor_GetNode(GF_Node *node, TestSensor *ts)
{
	GF_FieldInfo field;
	memset(ts, 0, sizeof(TestSensor));
	ts->sgprivate = node->sgprivate;

	CHECK_FIELD("TestSensor", 0, GF_SG_VRML_SFBOOL);
	if (field.eventType != GF_SG_EVENT_IN) return GF_FALSE;
	ts->onTrigger = *(SFBool *)field.far_ptr;

	CHECK_FIELD("TestSensor", 1, GF_SG_VRML_SFFLOAT);
	if (field.eventType != GF_SG_EVENT_EXPOSED_FIELD) return GF_FALSE;
	ts->value = *(SFFloat *)field.far_ptr;

	CHECK_FIELD("TestSensor", 2, GF_SG_VRML_SFFLOAT);
	if (field.eventType != GF_SG_EVENT_OUT) return GF_FALSE;

	return GF_TRUE;
}


static void TraverseTestSensor(GF_Node *node, void *rs, Bool is_destroy)
{
	TestSensorStack *stack = (TestSensorStack *)gf_node_get_private(node);

	if (is_destroy) {
		gf_free(stack);
		return;
	}
}

void TestSensor_OnTrigger(GF_Node *node, struct _route *route)
{
	GF_FieldInfo field;
	Fixed value;
	TestSensorStack *stack = (TestSensorStack *)gf_node_get_private(node);
	TestSensor_GetNode(node, &stack->ts);

	if (stack->ts.onTrigger) {
		value = stack->ts.value;
	} else {
		value = 1-stack->ts.value;
	}

	gf_node_get_field(node, 2, &field);
	*(SFFloat*)field.far_ptr = value;
	gf_node_event_out(node, 2);
}

void compositor_init_test_sensor(GF_Compositor *compositor, GF_Node *node)
{
	TestSensor ts;
	if (TestSensor_GetNode(node, &ts)) {
		GF_Err e;
		TestSensorStack *stack;
		GF_SAFEALLOC(stack, TestSensorStack);
		if (!stack) {
			GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to allocate test sensor stack\n"));
			return;
		}
		gf_node_set_private(node, stack);
		gf_node_set_callback_function(node, TraverseTestSensor);
		stack->ts = ts;

		e = gf_node_set_proto_eventin_handler(node, 0, TestSensor_OnTrigger);
		if (e) {
			GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to initialize Proto TestSensor callback: %s\n", gf_error_to_string(e) ));
		}
	} else {
		GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Compositor] Unable to initialize test sensor\n"));
	}
}


/*CustomTexture: tests defining new (OpenGL) textures*/
typedef struct
{
    BASE_NODE
    
    Fixed intensity;
} CustomTexture;

typedef struct
{
    CustomTexture tx;
    GF_TextureHandler txh;
    u32 gl_id;
    Bool disabled;
} CustomTextureStack;

static Bool CustomTexture_GetNode(GF_Node *node, CustomTexture *tx)
{
    GF_FieldInfo field;
    memset(tx, 0, sizeof(CustomTexture));
    tx->sgprivate = node->sgprivate;
    
    CHECK_FIELD("CustomTexture", 0, GF_SG_VRML_SFFLOAT);
	if (field.eventType != GF_SG_EVENT_EXPOSED_FIELD) return GF_FALSE;
    tx->intensity = *(SFFloat *)field.far_ptr;
    
    return GF_TRUE;
}

static void TraverseCustomTexture(GF_Node *node, void *rs, Bool is_destroy)
{
    CustomTextureStack *stack = (CustomTextureStack *)gf_node_get_private(node);
    
    if (is_destroy) {
        //release texture object
        gf_sc_texture_destroy(&stack->txh);
        gf_free(stack);
        return;
    }
}

#ifndef GPAC_DISABLE_3D
#include "gl_inc.h"
#endif

static void CustomTexture_update(GF_TextureHandler *txh)
{
#ifndef GPAC_DISABLE_3D
    u8 data[16];
#endif
    CustomTextureStack *stack = gf_node_get_private(txh->owner);
    //alloc texture
    if (!txh->tx_io) {
        //allocate texture
        gf_sc_texture_allocate(txh);
        if (!txh->tx_io) return;
    }
    if (stack->disabled) return;
    
#ifndef GPAC_DISABLE_3D
    //texture not setup, do it
    if (! gf_sc_texture_get_gl_id(txh)) {
        
        //setup some defaults (these two vars are used to setup internal texture format)
        //in our case we only want to test OpenGL so no need to fill in the texture width/height stride
        //since we will upload ourselves the texture
        txh->transparent = 0;
        txh->pixelformat = GF_PIXEL_RGB;

        //signaling we modified associated data (even if no data in our case) to mark texture as dirty
        gf_sc_texture_set_data(txh);
        
        //trigger HW setup of the texture
        gf_sc_texture_push_image(txh, GF_FALSE, GF_FALSE);
        
        //OK we have a valid textureID
        stack->gl_id = gf_sc_texture_get_gl_id(txh);
    }
#endif


    //get current value of node->value
    CustomTexture_GetNode(txh->owner, &stack->tx);

#ifndef GPAC_DISABLE_3D
    //setup our texture data
    memset(data, 0, sizeof(char)*12);
    data[0] = (u8) (0xFF * FIX2FLT(stack->tx.intensity)); //first pixel red modulated by intensity
    data[4] = (u8) (0xFF * FIX2FLT(stack->tx.intensity)); //second pixel green
    data[8] = (u8) (0xFF * FIX2FLT(stack->tx.intensity)); //third pixel blue
    //last pixel black
    
    glBindTexture( GL_TEXTURE_2D, stack->gl_id);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 2, 2, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
    
#endif
 
}

void compositor_init_custom_texture(GF_Compositor *compositor, GF_Node *node)
{
    CustomTexture tx;
    if (CustomTexture_GetNode(node, &tx)) {
        CustomTextureStack *stack;
        GF_SAFEALLOC(stack, CustomTextureStack);
		if (!stack) {
			GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to allocate custom texture group stack\n"));
			return;
		}
        gf_node_set_private(node, stack);
        gf_node_set_callback_function(node, TraverseCustomTexture);
        stack->tx = tx;

		if (!gf_sc_check_gl_support(compositor)) {
			stack->disabled = GF_TRUE;
			GF_LOG(GF_LOG_WARNING, GF_LOG_COMPOSE, ("[Compositor] Driver disabled, cannot render custom texture test\n"));
			return;
		}
        //register texture object
        gf_sc_texture_setup(&stack->txh, compositor, node);
        stack->txh.update_texture_fcnt = CustomTexture_update;
    
    } else {
        GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Compositor] Unable to initialize custom texture\n"));
    }
}

#ifndef GPAC_DISABLE_3D

static void TraverseVRGeometry(GF_Node *node, void *rs, Bool is_destroy)
{
	GF_TextureHandler *txh;
	GF_MediaObjectVRInfo vrinfo;
	GF_MeshSphereAngles sphere_angles;
	Bool mesh_was_reset = GF_FALSE;
	GF_TraverseState *tr_state = (GF_TraverseState *)rs;
	Drawable3D *stack = (Drawable3D *)gf_node_get_private(node);

	if (is_destroy) {
		drawable_3d_del(node);
		return;
	}

	if (!tr_state->appear || ! ((M_Appearance *)tr_state->appear)->texture)
		return;
	
	txh = gf_sc_texture_get_handler( ((M_Appearance *) tr_state->appear)->texture );
	if (!txh->stream) return;

	if (gf_node_dirty_get(node) || (tr_state->traversing_mode==TRAVERSE_DRAW_3D)) {
		if (! gf_mo_get_srd_info(txh->stream, &vrinfo))
			return;

		if (vrinfo.has_full_coverage && tr_state->disable_partial_sphere) {
			if ((vrinfo.srd_w!=vrinfo.srd_max_x) || (vrinfo.srd_h!=vrinfo.srd_max_y))
				return;
		}

		sphere_angles.min_phi = -GF_PI2 + GF_PI * vrinfo.srd_y / vrinfo.srd_max_y;
		sphere_angles.max_phi = -GF_PI2 + GF_PI * (vrinfo.srd_h +  vrinfo.srd_y) / vrinfo.srd_max_y;

		sphere_angles.min_theta = GF_2PI * vrinfo.srd_x / vrinfo.srd_max_x;
		sphere_angles.max_theta = GF_2PI * ( vrinfo.srd_w + vrinfo.srd_x ) / vrinfo.srd_max_x;

		if (gf_node_dirty_get(node)) {
			u32 radius;
			mesh_reset(stack->mesh);

			radius = MAX(vrinfo.scene_width, vrinfo.scene_height) / 4;
			//may happen that we don't have a scene width/height, use hardcoded 100 units radius (size actually doesn't matter
			//since our VP/camera is at the center of the sphere
			if (!radius) {
				radius = 100;
			}

			if (radius) {
				mesh_new_sphere(stack->mesh, -1 * INT2FIX(radius), GF_FALSE, &sphere_angles);

				txh->flags &= ~GF_SR_TEXTURE_REPEAT_S;
				txh->flags &= ~GF_SR_TEXTURE_REPEAT_T;
			}
			mesh_was_reset = GF_TRUE;
			gf_node_dirty_clear(node, GF_SG_NODE_DIRTY);
		}
		
		
		if (tr_state->traversing_mode==TRAVERSE_DRAW_3D) {
			Bool visible = GF_FALSE;
#ifndef GPAC_DISABLE_LOG
			const char *pid_name = gf_filter_pid_get_name(txh->stream->odm->pid);
#endif
			if (! tr_state->camera_was_dirty && !mesh_was_reset) {
				visible = (stack->mesh->flags & MESH_WAS_VISIBLE) ? GF_TRUE : GF_FALSE;
			} else if ((vrinfo.srd_w==vrinfo.srd_max_x) && (vrinfo.srd_h==vrinfo.srd_max_y)) {
				visible = GF_TRUE;
			}
			else if (txh->compositor->tvtf) {
				visible = GF_TRUE;
			//estimate visibility asap, even if texture not yet ready (we have SRD info):
			//this allows sending stop commands which will free inactive decoder HW context
			} else {
				u32 i, j;
				u32 nb_visible=0;
				u32 nb_tests = tr_state->visual->compositor->tvtn;
				u32 min_visible_threshold = tr_state->visual->compositor->tvtt;
				u32 stride;

				//pick nb_tests vertices spaced every stride in the mesh
				stride = stack->mesh->v_count;
				stride /= nb_tests;
				for (i=0; i<nb_tests; i++) {
					Bool vis = GF_TRUE;
					GF_Vec pt = stack->mesh->vertices[i*stride].pos;
					//check the point is in our frustum - don't test far plane
					for (j=1; j<6; j++) {
						Fixed d = gf_plane_get_distance(&tr_state->camera->planes[j], &pt);
						if (d<0) {
							vis = GF_FALSE;
							break;
						}
					}
					if (vis) {
						nb_visible++;
						//abort test if more visible points than our threshold
						if (nb_visible > min_visible_threshold)
							break;
					}
				}
				if (nb_visible > min_visible_threshold) 
					visible = GF_TRUE;
				GF_LOG(GF_LOG_INFO, GF_LOG_COMPOSE, ("[Compositor] Texture %s Partial sphere is %s - %d sample points visible out of %d\n", pid_name, visible ? "visible" : "hidden",  nb_visible, i));
			}

			if (visible) {
				stack->mesh->flags |= MESH_WAS_VISIBLE;
			} else {
				stack->mesh->flags &= ~MESH_WAS_VISIBLE;
			}

			if (visible && (vrinfo.srd_w != vrinfo.srd_max_x) && tr_state->visual->compositor->gaze_changed) {
				s32 gx, gy;
				tr_state->visual->compositor->hit_node = NULL;
				tr_state->visual->compositor->hit_square_dist = 0;

				//gaze coords are 0,0 in top-left
				gx = (s32)( tr_state->visual->compositor->gaze_x - tr_state->camera->width/2 );
				gy = (s32)( tr_state->camera->height/2 - tr_state->visual->compositor->gaze_y );

				visual_3d_setup_ray(tr_state->visual, tr_state, gx, gy);
				visual_3d_vrml_drawable_pick(node, tr_state, stack->mesh, NULL);
				if (tr_state->visual->compositor->hit_node) {
					GF_LOG(GF_LOG_INFO, GF_LOG_COMPOSE, ("[Compositor] Texture %s Partial sphere is under gaze coord %d %d\n", pid_name, tr_state->visual->compositor->gaze_x, tr_state->visual->compositor->gaze_y));

					tr_state->visual->compositor->hit_node = NULL;
				} else {
					visible = GF_FALSE;
				}

			}

			if (vrinfo.has_full_coverage) {
				if (visible) {
					if (!txh->is_open) {
						GF_LOG(GF_LOG_INFO, GF_LOG_COMPOSE, ("[Compositor] Texture %s stopped on visible partial sphere - starting it\n", pid_name));
						if (txh->stream->odm) {
							txh->stream->odm->disable_buffer_at_next_play = GF_TRUE;
							txh->stream->odm->flags |= GF_ODM_TILED_SHARED_CLOCK;
							gf_sc_texture_play_from_to(txh, NULL, -1, -1, 1, 0);
						}
					}

					if (txh->data) {
						Bool do_show = GF_TRUE;
						Bool is_full_cover =  (vrinfo.srd_w == vrinfo.srd_max_x) ? GF_TRUE : GF_FALSE;
						visual_3d_enable_depth_buffer(tr_state->visual, GF_FALSE);
						visual_3d_enable_antialias(tr_state->visual, GF_FALSE);
						if ((tr_state->visual->compositor->tvtd == TILE_DEBUG_FULL) && !is_full_cover) do_show = GF_FALSE;
						else if ((tr_state->visual->compositor->tvtd == TILE_DEBUG_PARTIAL) && is_full_cover) do_show = GF_FALSE;

						if (do_show) {
							visual_3d_draw(tr_state, stack->mesh);
						}
						visual_3d_enable_depth_buffer(tr_state->visual, GF_TRUE);
					}
				} else {
					if (txh->is_open) {
						GF_LOG(GF_LOG_INFO, GF_LOG_COMPOSE, ("[Compositor] Texture %s playing on hidden partial sphere - stopping it\n", pid_name));
						gf_sc_texture_stop_no_unregister(txh);
					}
				}
			} else {
				if (txh->data) {
					visual_3d_enable_depth_buffer(tr_state->visual, GF_FALSE);
					visual_3d_enable_antialias(tr_state->visual, GF_FALSE);
					visual_3d_draw(tr_state, stack->mesh);
					visual_3d_enable_depth_buffer(tr_state->visual, GF_TRUE);
				}
				if (!tr_state->disable_partial_sphere) {
					if (visible) {
						gf_mo_hint_quality_degradation(txh->stream, 0);
					} else {
						gf_mo_hint_quality_degradation(txh->stream, 100);
					}
				}
			}
		}
	}
	if (tr_state->traversing_mode==TRAVERSE_GET_BOUNDS) {
		tr_state->bbox = stack->mesh->bounds;
	}
}


static void compositor_init_vr_geometry(GF_Compositor *compositor, GF_Node *node)
{
	drawable_3d_new(node);
	gf_node_set_callback_function(node, TraverseVRGeometry);
}

#define VRHUD_SCALE	6
static void TraverseVRHUD(GF_Node *node, void *rs, Bool is_destroy)
{
	GF_TraverseState *tr_state = (GF_TraverseState *) rs;
	GF_Matrix mv_bck, proj_bck, cam_bck;
	/*SFVec3f target;*/
	GF_Rect vp, orig_vp;
	u32 mode, i, cull_bck;
	Fixed angle_yaw/*, angle_pitch*/;
	SFVec3f axis;
	GF_Node *subtree = gf_node_get_private(node);
	if (is_destroy) return;

	if (!tr_state->camera) return;
	mode = tr_state->visual->compositor->vrhud_mode;
	if (!mode) return;

	gf_mx_copy(mv_bck, tr_state->model_matrix);
	gf_mx_copy(cam_bck, tr_state->camera->modelview);

	tr_state->disable_partial_sphere = GF_TRUE;
	/*target = tr_state->camera->target;*/
	orig_vp = tr_state->camera->proj_vp;

/*
	//compute pitch (elevation)
	dlen = tr_state->camera->target.x*tr_state->camera->target.x + tr_state->camera->target.z*tr_state->camera->target.z;
	dlen = gf_sqrt(dlen);
*/

	/*angle_pitch = gf_atan2(tr_state->camera->target.y, dlen);*/

	//compute yaw (rotation Y)
	angle_yaw = gf_atan2(tr_state->camera->target.z, tr_state->camera->target.x);

	//compute axis for the pitch
	axis = tr_state->camera->target;
	axis.y=0;
	gf_vec_norm(&axis);

	visual_3d_enable_depth_buffer(tr_state->visual, GF_FALSE);

	if (mode==2) {
		//rear mirror, reverse x-axis on projection
		tr_state->camera->projection.m[0] *= -1;
		visual_3d_projection_matrix_modified(tr_state->visual);
		//inverse backface culling
		tr_state->reverse_backface = GF_TRUE;
		vp = orig_vp;
		vp.width/=VRHUD_SCALE;
		vp.height/=VRHUD_SCALE;
		vp.x = orig_vp.x + (orig_vp.width-vp.width)/2;
		vp.y = orig_vp.y + orig_vp.height-vp.height;

		visual_3d_set_viewport(tr_state->visual, vp);

		gf_mx_add_rotation(&tr_state->model_matrix, GF_PI, tr_state->camera->up.x, tr_state->camera->up.y, tr_state->camera->up.z);
		gf_node_traverse(subtree, rs);
		tr_state->camera->projection.m[0] *= -1;
		visual_3d_projection_matrix_modified(tr_state->visual);
	} else if (mode==1) {
		gf_mx_copy(proj_bck, tr_state->camera->projection);
		gf_mx_init(tr_state->camera->modelview);

		//force projection with PI/2 fov and AR 1:1
		tr_state->camera->projection.m[0] = -1;
		tr_state->camera->projection.m[5] = 1;
		visual_3d_projection_matrix_modified(tr_state->visual);
		//force cull inside
		cull_bck = tr_state->cull_flag;
		tr_state->cull_flag = CULL_INSIDE;
		//inverse backface culling
		tr_state->reverse_backface = GF_TRUE;

		//draw 3 viewports, each separated by PI/2 rotation
		for (i=0; i<3; i++) {
			vp = orig_vp;
			vp.height/=VRHUD_SCALE;
			vp.width=vp.height;
			//we reverse X in the projection, so reverse the viewports
			vp.x = orig_vp.x + orig_vp.width/2 - 3*vp.width/2 + (3-i-1)*vp.width;
			vp.y = orig_vp.y + orig_vp.height - vp.height;
			visual_3d_set_viewport(tr_state->visual, vp);
			tr_state->disable_cull = GF_TRUE;

			gf_mx_init(tr_state->model_matrix);
			gf_mx_add_rotation(&tr_state->model_matrix, angle_yaw-GF_PI, 0, 1, 0);
			gf_mx_add_rotation(&tr_state->model_matrix, i*GF_PI2, 0, 1, 0);

			gf_node_traverse(subtree, rs);
		}
		gf_mx_copy(tr_state->camera->projection, proj_bck);
		visual_3d_projection_matrix_modified(tr_state->visual);
		tr_state->cull_flag = cull_bck;
		gf_mx_copy(tr_state->camera->modelview, cam_bck);
	}
	else if ((mode==4) || (mode==3)) {
		// mirror, reverse x-axis on projection
		tr_state->camera->projection.m[0] *= -1;
		visual_3d_projection_matrix_modified(tr_state->visual);
		//inverse backface culling
		tr_state->reverse_backface = GF_TRUE;

		//side left view
		vp = orig_vp;
		vp.width/=VRHUD_SCALE;
		vp.height/=VRHUD_SCALE;
		if (mode==3) {
			vp.x = orig_vp.x;
		} else {
			vp.x = orig_vp.x + orig_vp.width/2 - 2*vp.width;
		}
		vp.y = orig_vp.y + orig_vp.height - vp.height;
		visual_3d_set_viewport(tr_state->visual, vp);

		gf_mx_add_rotation(&tr_state->model_matrix, -2*GF_PI/3, 0, 1, 0);

		gf_node_traverse(subtree, rs);

		//side right view
		if (mode==3) {
			vp.x = orig_vp.x + orig_vp.width - vp.width;
		} else {
			vp.x = orig_vp.x + orig_vp.width/2+vp.width;
		}
		visual_3d_set_viewport(tr_state->visual, vp);

		gf_mx_copy(tr_state->model_matrix, mv_bck);
		gf_mx_add_rotation(&tr_state->model_matrix, 2*GF_PI/3, 0, 1, 0);

		gf_node_traverse(subtree, rs);

		if (mode==4) {
			//upper view
			vp.x = orig_vp.x + orig_vp.width/2 - vp.width;
			visual_3d_set_viewport(tr_state->visual, vp);

			gf_mx_copy(tr_state->model_matrix, mv_bck);
			gf_mx_add_rotation(&tr_state->model_matrix, - GF_PI2, -axis.z, 0, axis.x);
			gf_node_traverse(subtree, rs);

			//down view
			vp.x = orig_vp.x + orig_vp.width/2;
			visual_3d_set_viewport(tr_state->visual, vp);

			gf_mx_copy(tr_state->model_matrix, mv_bck);
			gf_mx_add_rotation(&tr_state->model_matrix, GF_PI2, -axis.z, 0, axis.x);

			gf_node_traverse(subtree, rs);
		}

		tr_state->camera->projection.m[0] *= -1;
		visual_3d_projection_matrix_modified(tr_state->visual);
	}

	//restore camera and VP
	gf_mx_copy(tr_state->model_matrix, mv_bck);
	visual_3d_set_viewport(tr_state->visual, orig_vp);
	visual_3d_enable_depth_buffer(tr_state->visual, GF_TRUE);
	tr_state->disable_partial_sphere = GF_FALSE;
	tr_state->reverse_backface = GF_FALSE;
}

void compositor_init_vrhud(GF_Compositor *compositor, GF_Node *node)
{
	GF_Node *n;
	GF_SceneGraph *sg = gf_node_get_graph(node);
	sg = gf_sg_get_parent(sg);

	n = gf_sg_find_node_by_name(sg, "DYN_TRANS");
	if (!n) {
		GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Unable to initialize VRHUD group, no main scene\n"));
	} else {
		gf_node_set_callback_function(node, TraverseVRHUD);
		gf_node_proto_set_grouping(node);
		gf_node_set_private(node, n);
	}
}

void get_tx_coords_from_angle(GF_TraverseState *tr_state, GF_TextureHandler *txh, Bool horizontal, u32 *min_coord, u32 *max_coord);

static void TraverseSRDSphere(GF_Node *node, void *rs, Bool is_destroy)
{
	GF_TextureHandler *txh;
	GF_MediaObjectVRInfo vrinfo;
	GF_TraverseState *tr_state = (GF_TraverseState *)rs;
	Drawable3D *stack = (Drawable3D *)gf_node_get_private(node);

	if (is_destroy) {
		drawable_3d_del(node);
		return;
	}

	if (!tr_state->appear || ! ((M_Appearance *)tr_state->appear)->texture)
		return;

	txh = gf_sc_texture_get_handler( ((M_Appearance *) tr_state->appear)->texture );
	if (!txh->stream) return;

	if (gf_node_dirty_get(node) || txh->stream->srd_map_changed || (tr_state->traversing_mode==TRAVERSE_DRAW_3D)) {
		if (! gf_mo_get_srd_info(txh->stream, &vrinfo))
			return;

		if (gf_node_dirty_get(node) || txh->stream->srd_map_changed) {
			u32 radius;
			mesh_reset(stack->mesh);

			radius = MAX(vrinfo.scene_width, vrinfo.scene_height) / 4;
			//may happen that we don't have a scene width/height, use hardcoded 100 units radius (size actually doesn't matter
			//since our VP/camera is at the center of the sphere
			if (!radius) radius = 100;

			const GF_PropertyValue *srd = NULL;
			const GF_PropertyValue *srd_ref = NULL;
			if (txh->stream->odm && txh->stream->odm->pid) {
				srd = gf_filter_pid_get_property(txh->stream->odm->pid, GF_PROP_PID_SRD_MAP);
				srd_ref = gf_filter_pid_get_property(txh->stream->odm->pid, GF_PROP_PID_SRD_REF);
			}
			if (srd && srd_ref) {
void mesh_new_spherical_srd(GF_Mesh *mesh, Fixed radius, const GF_PropertyValue *srd_map, const GF_PropertyValue *srd_ref);
				mesh_new_spherical_srd(stack->mesh, -1 * INT2FIX(radius), srd, srd_ref);
			} else {
				mesh_new_sphere(stack->mesh, -1 * INT2FIX(radius), GF_FALSE, NULL);
			}
			txh->flags &= ~GF_SR_TEXTURE_REPEAT_S;
			txh->flags &= ~GF_SR_TEXTURE_REPEAT_T;
			gf_node_dirty_clear(node, GF_SG_NODE_DIRTY);
			txh->stream->srd_map_changed = GF_FALSE;
		}

		if (tr_state->traversing_mode==TRAVERSE_DRAW_3D) {
#ifndef GPAC_DISABLE_LOG
			const char *pid_name = gf_filter_pid_get_name(txh->stream->odm->pid);
#endif
			stack->mesh->flags |= MESH_WAS_VISIBLE;

			visual_3d_enable_depth_buffer(tr_state->visual, GF_FALSE);
			visual_3d_enable_antialias(tr_state->visual, GF_FALSE);
			visual_3d_draw(tr_state, stack->mesh);
			visual_3d_enable_depth_buffer(tr_state->visual, GF_TRUE);

			if (!tr_state->visual->compositor->gazer_enabled) {
				u32 min_x, max_x, min_y, max_y;

				get_tx_coords_from_angle(tr_state, txh, GF_TRUE, &min_x, &max_x);
				get_tx_coords_from_angle(tr_state, txh, GF_FALSE, &min_y, &max_y);
				if (min_x!=max_x) {
					gf_mo_hint_visible_rect(txh->stream, min_x, max_x, min_y, max_y);
				}
			}
			else if (tr_state->visual->compositor->gaze_changed) {
				s32 gx, gy;
				tr_state->visual->compositor->hit_node = NULL;
				tr_state->visual->compositor->hit_square_dist = 0;

				//gaze coords are 0,0 in top-left
				gx = (s32)( tr_state->visual->compositor->gaze_x - tr_state->camera->width/2 );
				gy = (s32)( tr_state->camera->height/2 - tr_state->visual->compositor->gaze_y );

				visual_3d_setup_ray(tr_state->visual, tr_state, gx, gy);
				visual_3d_vrml_drawable_pick(node, tr_state, stack->mesh, NULL);
				if (tr_state->visual->compositor->hit_node) {
					const GF_PropertyValue *srd = NULL;
					const GF_PropertyValue *srd_ref = NULL;
					if (txh->stream->odm && txh->stream->odm->pid) {
						srd = gf_filter_pid_get_property(txh->stream->odm->pid, GF_PROP_PID_SRD_MAP);
						srd_ref = gf_filter_pid_get_property(txh->stream->odm->pid, GF_PROP_PID_SRD_REF);
					}
					if (srd && srd_ref) {
						s32 orig_x = FIX2INT( tr_state->visual->compositor->hit_texcoords.x * srd_ref->value.vec2i.x );
						s32 orig_y = FIX2INT( (FIX_ONE - tr_state->visual->compositor->hit_texcoords.y) * srd_ref->value.vec2i.y);

						u32 i, nb_items = srd->value.uint_list.nb_items / 8;
						u32 *vals = srd->value.uint_list.vals;
						for (i=0; i<nb_items; i++) {
							if ((u32) orig_x < vals[8*i+4]) continue;
							if ((u32) orig_x > vals[8*i+4] + vals[8*i+6]) continue;

							if ((u32) orig_y < vals[8*i+5]) continue;
							if ((u32) orig_y > vals[8*i+5] + vals[8*i+7]) continue;

							orig_x -= vals[8*i+4]; //remove tx orig
							orig_y -= vals[8*i+5]; //remove ty orig

							s32 srd_x = vals[8*i] + orig_x * vals[8*i+2] / vals[8*i+6];
							s32 srd_y = vals[8*i+1] + orig_y * vals[8*i+3] / vals[8*i+7];

							gf_mo_hint_gaze(txh->stream, (u32)srd_x, (u32)srd_y);
							break;
						}
					}
					GF_LOG(GF_LOG_INFO, GF_LOG_COMPOSE, ("[Compositor] Texture %s Partial sphere is under gaze coord %d %d\n", pid_name, tr_state->visual->compositor->gaze_x, tr_state->visual->compositor->gaze_y));

					tr_state->visual->compositor->hit_node = NULL;
				}
			}
		}
	}

	if (tr_state->traversing_mode==TRAVERSE_GET_BOUNDS) {
		tr_state->bbox = stack->mesh->bounds;
	}
}
static void compositor_init_srd_sphere(GF_Compositor *compositor, GF_Node *node)
{
	drawable_3d_new(node);
	gf_node_set_callback_function(node, TraverseSRDSphere);
}

#endif //GPAC_DISABLE_3D

/*hardcoded proto loading - this is mainly used for module development and testing...*/
void gf_sc_init_hardcoded_proto(GF_Compositor *compositor, GF_Node *node)
{
	MFURL *proto_url;
	GF_Proto *proto;
	u32 i, j;
	GF_HardcodedProto *ifce;

	proto = gf_node_get_proto(node);
	if (!proto) return;
	proto_url = gf_sg_proto_get_extern_url(proto);

	for (i=0; i<proto_url->count; i++) {
		const char *url = proto_url->vals[0].url;
        if (!url) continue;
        
#ifndef GPAC_DISABLE_3D
		if (!strcmp(url, "urn:inet:gpac:builtin:PathExtrusion")) {
			compositor_init_path_extrusion(compositor, node);
			return;
		}
		if (!strcmp(url, "urn:inet:gpac:builtin:PlanarExtrusion")) {
			compositor_init_planar_extrusion(compositor, node);
			return;
		}
		if (!strcmp(url, "urn:inet:gpac:builtin:PlaneClipper")) {
			compositor_init_plane_clipper(compositor, node);
			return;
		}
        if (!strcmp(url, "urn:inet:gpac:builtin:VRGeometry")) {
            compositor_init_vr_geometry(compositor, node);
            return;
        }
        if (!strcmp(url, "urn:inet:gpac:builtin:VRHUD")) {
            compositor_init_vrhud(compositor, node);
            return;
        }
        if (!strcmp(url, "urn:inet:gpac:builtin:SRDSphere")) {
            compositor_init_srd_sphere(compositor, node);
            return;
        }
#endif
		if (!strcmp(url, "urn:inet:gpac:builtin:OffscreenGroup")) {
			compositor_init_offscreen_group(compositor, node);
			return;
		}
		if (!strcmp(url, "urn:inet:gpac:builtin:DepthGroup")) {
			compositor_init_depth_group(compositor, node);
			return;
		}
		if (!strcmp(url, "urn:inet:gpac:builtin:DepthViewPoint")) {
			compositor_init_depth_viewpoint(compositor, node);
			return;
		}
		if (!strcmp(url, "urn:inet:gpac:builtin:IndexedCurve2D")) {
			compositor_init_idx_curve2d(compositor, node);
			return;
		}
		if (!strcmp(url, "urn:inet:gpac:builtin:Untransform")) {
			compositor_init_untransform(compositor, node);
			return;
		}
		if (!strcmp(url, "urn:inet:gpac:builtin:FlashShape")) {
#ifdef GPAC_ENABLE_FLASHSHAPE
			compositor_init_hc_flashshape(compositor, node);
#endif
			return;
		}
		if (!strcmp(url, "urn:inet:gpac:builtin:StyleGroup")) {
			compositor_init_style_group(compositor, node);
			return;
		}
		if (!strcmp(url, "urn:inet:gpac:builtin:TestSensor")) {
			compositor_init_test_sensor(compositor, node);
			return;
		}
        if (!strcmp(url, "urn:inet:gpac:builtin:CustomTexture")) {
            compositor_init_custom_texture(compositor, node);
            return;
        }


		/*check proto modules*/
		if (compositor->proto_modules) {
			j = 0;
			while ( (ifce = (GF_HardcodedProto *)gf_list_enum(compositor->proto_modules, &j) )) {
				if ( ifce->can_load_proto(url) && ifce->init(ifce, compositor, node, url) ) {
					return;
				}
			}
		}
	}

}

Bool gf_sc_uri_is_hardcoded_proto(GF_Compositor *compositor, const char *uri)
{
	/*check proto modules*/
	if (compositor && compositor->proto_modules ) {
		u32 j = 0;
		GF_HardcodedProto *ifce;
		while ( (ifce = (GF_HardcodedProto *)gf_list_enum(compositor->proto_modules, &j) )) {
			if ( ifce->can_load_proto(uri)) {
				return GF_TRUE;
			}
		}
	}
	return GF_FALSE;
}

GF_TextureHandler *gf_sc_hardcoded_proto_get_texture_handler(GF_Node *n)
{
    
    MFURL *proto_url;
    GF_Proto *proto;
    u32 i;
    
    proto = gf_node_get_proto(n);
    if (!proto) return NULL;
    proto_url = gf_sg_proto_get_extern_url(proto);
    
    for (i=0; i<proto_url->count; i++) {
        const char *url = proto_url->vals[0].url;
        if (!strcmp(url, "urn:inet:gpac:builtin:CustomTexture")) {
            CustomTextureStack *stack = gf_node_get_private(n);
            if (stack) return &stack->txh;
        }
    }
    return NULL;
}

#endif /*GPAC_DISABLE_VRML*/

#endif // GPAC_DISABLE_COMPOSITOR
