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

struct og_pos
{
	Fixed priority;
	u32 position;
};
typedef struct
{
	SFNode *owner;
	struct scene_renderer *compositor;
	GROUPINGNODESTACK
	u32 *positions;
} OrderedGroupStack;

static void DestroyOrderedGroup(SFNode *node)
{
	OrderedGroupStack *ptr = (OrderedGroupStack *) Node_GetPrivate(node);
	DeleteGroupingNode((GroupingNode *)ptr);
	if (ptr->positions) free(ptr->positions);
	free(ptr);
}
static s32 compare_priority(const void* elem1, const void* elem2)
{
	struct og_pos *p1, *p2;
	p1 = (struct og_pos *)elem1;
	p2 = (struct og_pos *)elem2;
	if (p1->priority < p2->priority) return -1;
	if (p1->priority > p2->priority) return 1;
	return 0;
}
static void RenderOrderedGroup(SFNode *node, void *rs)
{
	u32 i, count;
	struct og_pos *priorities;
	OrderedGroupStack *ogs = (OrderedGroupStack *) Node_GetPrivate(node);
	M_OrderedGroup *og = (M_OrderedGroup *)node;
	RenderEffect3D *eff = (RenderEffect3D *)rs;

	if (!og->order.count) {
		grouping_traverse((GroupingNode*)ogs, eff, NULL);
		return;
	}

	/*check whether the OrderedGroup node has changed*/
	if (Node_GetDirty(node) & SG_NODE_DIRTY) {
		if (ogs->positions) free(ogs->positions);
		count = ChainGetCount(og->children);
		priorities = malloc(sizeof(struct og_pos)*count);
		for (i=0; i<count; i++) {
			priorities[i].position = i;
			priorities[i].priority = (i<og->order.count) ? og->order.vals[i] : 0;
		}
		qsort(priorities, count, sizeof(struct og_pos), compare_priority);

		ogs->positions = malloc(sizeof(u32) * count);
		for (i=0; i<count; i++) ogs->positions[i] = priorities[i].position;
		free(priorities);
	}
	grouping_traverse((GroupingNode*)ogs, eff, ogs->positions);
}

void R3D_InitOrderedGroup(Render3D *sr, SFNode *node)
{
	OrderedGroupStack *ptr = malloc(sizeof(OrderedGroupStack));
	memset(ptr, 0, sizeof(OrderedGroupStack));
	SetupGroupingNode((GroupingNode*)ptr, sr->compositor, node, ((M_OrderedGroup *)node)->children);
	Node_SetPrivate(node, ptr);
	Node_SetPreDestroyFunction(node, DestroyOrderedGroup);
	Node_SetRenderFunction(node, RenderOrderedGroup);
}

static void DestroySwitch(SFNode *node)
{
	s32 *st = (s32 *)Node_GetPrivate(node);
	free(st);
}
static void RenderSwitch(SFNode *node, void *rs)
{
	u32 i, count;
	Chain *children;
	s32 whichChoice;
	Bool prev_switch;
	SFNode *child;
	s32 *last_switch = (s32 *)Node_GetPrivate(node);
	RenderEffect3D *eff; 

	eff = (RenderEffect3D *)rs;
	Node_ClearDirty(node);
	prev_switch = eff->trav_flags;

	/*WARNING: X3D/MPEG4 NOT COMPATIBLE*/
	if (Node_GetTag(node)==TAG_MPEG4_Switch) {
		children = ((M_Switch *)node)->choice;
		whichChoice = ((M_Switch *)node)->whichChoice;
	} else {
		children = ((X_Switch *)node)->children;
		whichChoice = ((X_Switch *)node)->whichChoice;
	}

	count = ChainGetCount(children);

	/*check changes in choice field*/
	if (*last_switch != whichChoice) {
		eff->trav_flags |= TF_SWITCHED_OFF;
		/*deactivation must be signaled because switch may contain audio nodes (I hate this spec!!!)*/
		for (i=0; i<count; i++) {
			if ((s32) i==whichChoice) continue;
			child = ChainGetEntry(children, i);
			Node_Render(child, eff);
		}
		eff->trav_flags &= ~TF_SWITCHED_OFF;
		*last_switch = whichChoice;
	}

	eff->trav_flags = prev_switch;
	if (whichChoice>=0) {
		child = ChainGetEntry(children, whichChoice);
		Node_Render(child, eff);
	}
}

void R3D_InitSwitch(Render3D *sr, SFNode *node)
{
	s32 *last_switch = malloc(sizeof(s32));
	*last_switch = -1;
	Node_SetPrivate(node, last_switch);
	Node_SetPreDestroyFunction(node, DestroySwitch);
	Node_SetRenderFunction(node, RenderSwitch);
}

typedef struct
{
	SFNode *owner;
	struct scene_renderer *compositor;
	GROUPINGNODESTACK
	M4ColorMatrix cmat;
} ColorTransformStack;

static void DestroyColorTransform(SFNode *n)
{
	ColorTransformStack *ptr = (ColorTransformStack *)Node_GetPrivate(n);
	DeleteGroupingNode((GroupingNode *)ptr);
	free(ptr);
}

/*ColorTransform*/
static void RenderColorTransform(SFNode *node, void *rs)
{
	M_ColorTransform *tr = (M_ColorTransform *)node;
	ColorTransformStack *ptr = (ColorTransformStack  *)Node_GetPrivate(node);
	RenderEffect3D *eff;
	eff = (RenderEffect3D *) rs;
	if (Node_GetDirty(node) & SG_NODE_DIRTY) {
		m4_cmx_set(&ptr->cmat, 
			tr->mrr , tr->mrg, tr->mrb, tr->mra, tr->tr, 
			tr->mgr , tr->mgg, tr->mgb, tr->mga, tr->tg, 
			tr->mbr, tr->mbg, tr->mbb, tr->mba, tr->tb, 
			tr->mar, tr->mag, tr->mab, tr->maa, tr->ta); 
	}

	/*note we don't clear dirty flag, this is done in traversing*/
	if (ptr->cmat.identity) {
		grouping_traverse((GroupingNode *) ptr, eff, NULL);
	} else {
		M4ColorMatrix m4_cmx_bck;
		Bool prev_cmat = eff->has_cmat;
		if (prev_cmat) {
			m4_cmx_copy(&m4_cmx_bck, &eff->color_mat);
			m4_cmx_multiply(&eff->color_mat, &ptr->cmat);
		} else {
			m4_cmx_copy(&eff->color_mat, &ptr->cmat);
		}
		eff->has_cmat = 1;
		grouping_traverse((GroupingNode *) ptr, eff, NULL);
		/*restore effects*/
		if (prev_cmat) m4_cmx_copy(&eff->color_mat, &m4_cmx_bck);
		eff->has_cmat = prev_cmat;
	}
}

void R3D_InitColorTransform(Render3D *sr, SFNode *node)
{
	ColorTransformStack *stack = malloc(sizeof(ColorTransformStack));
	SetupGroupingNode((GroupingNode *)stack, sr->compositor, node, ((M_ColorTransform *)node)->children);
	m4_cmx_init(&stack->cmat);
	Node_SetPrivate(node, stack);
	Node_SetPreDestroyFunction(node, DestroyColorTransform);
	Node_SetRenderFunction(node, RenderColorTransform);
}

static void RenderGroup(SFNode *node, void *rs)
{
	GroupingNode *group = (GroupingNode *) Node_GetPrivate(node);
	grouping_traverse(group, (RenderEffect3D*)rs, NULL);
}
void R3D_InitGroup(Render3D *sr, SFNode *node)
{
	GroupingNode *stack = malloc(sizeof(GroupingNode));
	SetupGroupingNode(stack, sr->compositor, node, ((M_Group *)node)->children);
	Node_SetPrivate(node, stack);
	Node_SetPreDestroyFunction(node, DestroyBaseGrouping);
	Node_SetRenderFunction(node, RenderGroup);
}

void RenderCollision(SFNode *node, void *rs)
{
	u32 collide_flags;
	SFVec3f last_point;
	Fixed last_dist;
	M_Collision *col = (M_Collision *)node;
	RenderEffect3D *eff = (RenderEffect3D *)rs;
	GroupingNode *group = (GroupingNode *) Node_GetPrivate(node);

	if (eff->traversing_mode != TRAVERSE_COLLIDE) {
		grouping_traverse(group, eff, NULL);
	} else if (col->collide) {

		collide_flags = eff->camera->collide_flags;
		last_dist = eff->camera->collide_dist;
		eff->camera->collide_flags &= 0;
		eff->camera->collide_dist = FIX_MAX;
		last_point = eff->camera->collide_point;

		if (col->proxy) {
			/*always check bounds to update any dirty node*/
			eff->traversing_mode = TRAVERSE_GET_BOUNDS;
			Node_Render(col->proxy, rs);

			eff->traversing_mode = TRAVERSE_COLLIDE;
			Node_Render(col->proxy, rs);
		} else {
			grouping_traverse(group, (RenderEffect3D*)rs, NULL);
		}
		if (eff->camera->collide_flags & CF_COLLISION) {
			col->collideTime = Node_GetSceneTime(node);
			Node_OnEventOutSTR(node, "collideTime");
			/*if not closer restore*/
			if (collide_flags && (last_dist<eff->camera->collide_dist)) {
				eff->camera->collide_flags = collide_flags;
				eff->camera->collide_dist = last_dist;
				eff->camera->collide_point = last_point;
			}
		} else {
			eff->camera->collide_flags = collide_flags;
			eff->camera->collide_dist = last_dist;
		}
	}
}

void R3D_InitCollision(Render3D *sr, SFNode *node)
{
	GroupingNode *stack = malloc(sizeof(GroupingNode));
	SetupGroupingNode(stack, sr->compositor, node, ((M_Group *)node)->children);
	Node_SetPrivate(node, stack);
	Node_SetPreDestroyFunction(node, DestroyBaseGrouping);
	Node_SetRenderFunction(node, RenderCollision);
}

/*for transform, transform2D & transformMatrix2D*/
typedef struct
{
	SFNode *owner;
	struct scene_renderer *compositor;
	GROUPINGNODESTACK
	M4Matrix mx;
	Bool has_scale;
} TransformStack;

static void DestroyTransform(SFNode *n)
{
	TransformStack *ptr = (TransformStack *)Node_GetPrivate(n);
	DeleteGroupingNode((GroupingNode *)ptr);
	free(ptr);
}

static void NewTransformStack(Render3D *sr, SFNode *node, Chain *children)
{
	TransformStack *st= malloc(sizeof(TransformStack));
	memset(st, 0, sizeof(TransformStack));
	m4_mx_init(st->mx);
	SetupGroupingNode((GroupingNode *)st, sr->compositor, node, children);
	Node_SetPrivate(node, st);
	Node_SetPreDestroyFunction(node, DestroyTransform);
}

#define TRANS_PUSH_MX	\
		if (eff->traversing_mode == TRAVERSE_SORT) {	\
			VS3D_PushMatrix(eff->surface); \
			VS3D_MultMatrix(eff->surface, st->mx.m);	\
		}	\

#define TRANS_POP_MX	if (eff->traversing_mode==TRAVERSE_SORT) VS3D_PopMatrix(eff->surface);

static void RenderTransform(SFNode *n, void *rs)
{
	M4Matrix m4_mx_bckup;
	Bool scale_backup;
	TransformStack *st = Node_GetPrivate(n);
	M_Transform *tr = (M_Transform *)n;
	RenderEffect3D *eff = (RenderEffect3D *)rs;

	if (Node_GetDirty(n) & SG_NODE_DIRTY) {
		Bool scale_rot, recenter;
		m4_mx_init(st->mx);
		if (tr->translation.x || tr->translation.y || tr->translation.z) 
			m4_mx_add_translation(&st->mx, tr->translation.x, tr->translation.y, tr->translation.z);
		recenter = (tr->center.x || tr->center.y || tr->center.z) ? 1 : 0;
		if (recenter) 
			m4_mx_add_translation(&st->mx, tr->center.x, tr->center.y, tr->center.z);

		if (tr->rotation.q) m4_mx_add_rotation(&st->mx, tr->rotation.q, tr->rotation.x, tr->rotation.y, tr->rotation.z);
		scale_rot = (tr->scaleOrientation.q) ? 1 : 0;
		if (scale_rot) 
			m4_mx_add_rotation(&st->mx, tr->scaleOrientation.q, tr->scaleOrientation.x, tr->scaleOrientation.y, tr->scaleOrientation.z);
		if ((tr->scale.x != FIX_ONE) || (tr->scale.y != FIX_ONE) || (tr->scale.z != FIX_ONE)) 
			m4_mx_add_scale(&st->mx, tr->scale.x, tr->scale.y, tr->scale.z);
		if (scale_rot) 
			m4_mx_add_rotation(&st->mx, -tr->scaleOrientation.q, tr->scaleOrientation.x, tr->scaleOrientation.y, tr->scaleOrientation.z);
		if (recenter) 
			m4_mx_add_translation(&st->mx, -tr->center.x, -tr->center.y, -tr->center.z);

		st->has_scale = ((tr->scale.x != FIX_ONE) || (tr->scale.y != FIX_ONE) || (tr->scale.z != FIX_ONE)) ? 1 : 0;
   	}

	/*for fun we allow 3D nodes under 2D layout, form & co*/

	m4_mx_copy(m4_mx_bckup, eff->model_matrix);
	m4_mx_add_matrix(&eff->model_matrix, &st->mx);
	scale_backup = eff->has_scale;
	if (st->has_scale) eff->has_scale = 1;

	TRANS_PUSH_MX
	
	/*note we don't clear dirty flag, this is done in traversing*/
	grouping_traverse((GroupingNode *) st, eff, NULL);

	TRANS_POP_MX	

	eff->has_scale = scale_backup;
	m4_mx_copy(eff->model_matrix, m4_mx_bckup);
	if (eff->traversing_mode==TRAVERSE_GET_BOUNDS) m4_mx_apply_bbox(&st->mx, &eff->bbox);
}

void R3D_InitTransform(Render3D *sr, SFNode *node)
{
	NewTransformStack(sr, node, ((M_Transform *)node)->children);
	Node_SetRenderFunction(node, RenderTransform);

}

static void RenderTransform2D(SFNode *node, void *rs)
{
	M4Matrix m4_mx_bckup;
	Bool scale_backup;
	M_Transform2D *tr = (M_Transform2D *)node;
	TransformStack *st = (TransformStack*)Node_GetPrivate(node);
	RenderEffect3D *eff = (RenderEffect3D *) rs;

	if (Node_GetDirty(node) & SG_NODE_DIRTY) {
		M4Matrix2D mx;
		m4_mx2d_init(mx);
		if ((tr->scale.x != FIX_ONE) || (tr->scale.y != FIX_ONE)) 
			m4_mx2d_add_scale_at(&mx, tr->scale.x, tr->scale.y, 0, 0, tr->scaleOrientation);
		if (tr->rotationAngle) 
			m4_mx2d_add_rotation(&mx, tr->center.x, tr->center.y, tr->rotationAngle);
		if (tr->translation.x || tr->translation.y) 
			m4_mx2d_add_translation(&mx, tr->translation.x, tr->translation.y);

		st->has_scale = ((tr->scale.x != FIX_ONE) || (tr->scale.y != FIX_ONE)) ? 1 : 0;
		m4_mx_from_mx2d(&st->mx, &mx);
	}

	m4_mx_copy(m4_mx_bckup, eff->model_matrix);
	m4_mx_add_matrix(&eff->model_matrix, &st->mx);
	scale_backup = eff->has_scale;
	if (st->has_scale) eff->has_scale = 1;

	TRANS_PUSH_MX

	/*note we don't clear dirty flag, this is done in traversing*/
	grouping_traverse((GroupingNode *) st, eff, NULL);

	TRANS_POP_MX
	
	eff->has_scale = scale_backup;
	m4_mx_copy(eff->model_matrix, m4_mx_bckup);

	if (eff->traversing_mode==TRAVERSE_GET_BOUNDS) 
		m4_mx_apply_bbox(&st->mx, &eff->bbox);
}
void R3D_InitTransform2D(Render3D *sr, SFNode *node)
{
	NewTransformStack(sr, node, ((M_Transform2D *)node)->children);
	Node_SetRenderFunction(node, RenderTransform2D);
}

void TM2D_GetMatrix(SFNode *n, M4Matrix *mx)
{
	M4Matrix2D mat;
	M_TransformMatrix2D *tm = (M_TransformMatrix2D*)n;
	m4_mx2d_init(mat);
	mat.m[0] = tm->mxx; mat.m[1] = tm->mxy; mat.m[2] = tm->tx;
	mat.m[3] = tm->myx; mat.m[4] = tm->myy; mat.m[5] = tm->ty;
	m4_mx_from_mx2d(mx, &mat);
}


/*TransformMatrix2D*/
static void RenderTransformMatrix2D(SFNode *node, void *rs)
{
	M4Matrix m4_mx_bckup;
	Bool scale_backup;
	M_TransformMatrix2D *tm = (M_TransformMatrix2D*)node;
	TransformStack *st = (TransformStack *) Node_GetPrivate(node);
	RenderEffect3D *eff = (RenderEffect3D *)rs;

	if (Node_GetDirty(node) & SG_NODE_DIRTY) {
		TM2D_GetMatrix(node, &st->mx);
		st->has_scale = ((tm->mxx != FIX_ONE) || (tm->myy != FIX_ONE) || tm->mxy || tm->myx) ? 1 : 0;
	}

	m4_mx_copy(m4_mx_bckup, eff->model_matrix);
	m4_mx_add_matrix(&eff->model_matrix, &st->mx);
	scale_backup = eff->has_scale;
	if (st->has_scale) eff->has_scale = 1;

	TRANS_PUSH_MX

	/*note we don't clear dirty flag, this is done in traversing*/
	grouping_traverse((GroupingNode *)st, eff, NULL);

	TRANS_POP_MX
	
	m4_mx_copy(eff->model_matrix, m4_mx_bckup);
	eff->has_scale = scale_backup;

	if (eff->traversing_mode==TRAVERSE_GET_BOUNDS) m4_mx_apply_bbox(&st->mx, &eff->bbox);
}


void R3D_InitTransformMatrix2D(Render3D *sr, SFNode *node)
{
	NewTransformStack(sr, node, ((M_TransformMatrix2D *)node)->children);
	Node_SetRenderFunction(node, RenderTransformMatrix2D);
}

static void RenderBillboard(SFNode *n, void *rs)
{
	M4Matrix m4_mx_bckup;
	TransformStack *st = Node_GetPrivate(n);
	M_Billboard *bb = (M_Billboard *)n;
	RenderEffect3D *eff = (RenderEffect3D *)rs;

	/*can't cache the matrix here*/
	m4_mx_init(st->mx);
	if (eff->camera->is_3D) {
		SFVec3f z, axis;
		Fixed axis_len;
		SFVec3f user_pos = eff->camera->position;

		m4_mx_apply_vec(&eff->model_matrix, &user_pos);
		m4_vec_norm(&user_pos);
		axis = bb->axisOfRotation;
		axis_len = m4_vec_len(axis);
		if (axis_len<M4_EPSILON_FLOAT) {
			SFVec3f x, y, t;
			/*get user's right in local coord*/
			m4_vec_diff(t, eff->camera->position, eff->camera->target);
			m4_vec_norm(&t);
			x = m4_vec_cross(eff->camera->up, t);
			m4_vec_norm(&x);
			m4_mx_rotate_vector(&eff->model_matrix, &x);
			m4_vec_norm(&x);
			/*get user's up in local coord*/
			y = eff->camera->up;
			m4_mx_rotate_vector(&eff->model_matrix, &y);
			m4_vec_norm(&y);
			z = m4_vec_cross(x, y);
			m4_vec_norm(&z);

			m4_mx_rotation_matrix_from_vectors(&st->mx, x, y, z);
			m4_mx_inverse(&st->mx);
		} else {
			SFVec3f tmp;
			Fixed d, cosw, sinw, angle;
			m4_vec_norm(&axis);
			/*map eye & z into plane with normal axis through 0.0*/
			d = -m4_vec_dot(axis, user_pos);
			tmp = m4_vec_scale(axis, d);
			m4_vec_add(user_pos, user_pos, tmp);
			m4_vec_norm(&user_pos);

			z.x = z.y = 0; z.z = FIX_ONE;
			d = -m4_vec_dot(axis, z);
			tmp = m4_vec_scale(axis, d);
			m4_vec_add(z, z, tmp);
			m4_vec_norm(&z);

			cosw = m4_vec_dot(user_pos, z);
			tmp = m4_vec_cross(user_pos, z);
			sinw = m4_vec_len(tmp);
			angle = m4_acos(cosw); 
			m4_vec_norm(&tmp);
			if ((sinw>0) && (m4_vec_dot(axis, tmp) > 0)) m4_vec_rev(axis);
			m4_mx_add_rotation(&st->mx, angle, axis.x, axis.y, axis.z);
		}
	}

	m4_mx_copy(m4_mx_bckup, eff->model_matrix);
	m4_mx_add_matrix(&eff->model_matrix, &st->mx);
	
	TRANS_PUSH_MX

	/*note we don't clear dirty flag, this is done in traversing*/
	grouping_traverse((GroupingNode *) st, eff, NULL);

	TRANS_POP_MX

	m4_mx_copy(eff->model_matrix, m4_mx_bckup);

	if (eff->traversing_mode==TRAVERSE_GET_BOUNDS) m4_mx_apply_bbox(&st->mx, &eff->bbox);
}

void R3D_InitBillboard(Render3D *sr, SFNode *node)
{
	NewTransformStack(sr, node, ((M_Billboard *)node)->children);
	Node_SetRenderFunction(node, RenderBillboard);

}

static void DestroyLOD(SFNode *node)
{
	s32 *stack = Node_GetPrivate(node);
	free(stack);
}

static void RenderLOD(SFNode *node, void *rs)
{
	Chain *children;
	MFFloat *ranges;
	SFVec3f pos, usr;
	SFNode *child;
	u32 which_child, nb_children;
	Fixed dist;
	Bool do_all;
	M4Matrix mx;
	SFVec3f center;
	RenderEffect3D *eff = (RenderEffect3D *)rs;
	s32 *prev_child= Node_GetPrivate(node);


	/*WARNING: X3D/MPEG4 NOT COMPATIBLE*/
	if (Node_GetTag(node) == TAG_MPEG4_LOD) {
		children = ((M_LOD *) node)->level;
		ranges = &((M_LOD *) node)->range;
		center = ((M_LOD *) node)->center;
	} else {
		children = ((X_LOD *) node)->children;
		ranges = &((X_LOD *) node)->range;
		center = ((X_LOD *) node)->center;
	}

	nb_children = ChainGetCount(children);
	if (!nb_children) return;
	
	/*can't cache the matric here*/
	usr = eff->camera->position;
	pos = center;
	m4_mx_copy(mx, eff->model_matrix);
	m4_mx_inverse(&mx);
	m4_mx_apply_vec(&mx, &usr);
	m4_vec_diff(pos, pos, usr);
	dist = m4_vec_len(pos);
	for (which_child=0; which_child<ranges->count; which_child++) {
		if (dist<ranges->vals[which_child]) break;
	}
	if (which_child>=nb_children) which_child = nb_children-1;

	/*check if we're traversing the same child or not for audio rendering*/
	do_all = 0;
	if (Node_GetDirty(node)) {
		Node_ClearDirty(node);
		do_all = 1;
	} else if ((s32) which_child != *prev_child) {
		*prev_child = which_child;
		do_all = 1;
	}
	
	if (do_all) {
		u32 i;
		Bool prev_flags = eff->trav_flags;
		eff->trav_flags |= TF_SWITCHED_OFF;
		for (i=0; i<nb_children; i++) {
			if (i==which_child) continue;
			child = ChainGetEntry(children, i);
			Node_Render(child, rs);
		}
		eff->trav_flags = prev_flags;
	}
	
	child = ChainGetEntry(children, which_child);
	Node_Render(child, rs);
}

void R3D_InitLOD(Render3D *sr, SFNode *node)
{
	s32 *stack = malloc(sizeof(s32));
	*stack = -1;
	Node_SetRenderFunction(node, RenderLOD);
	Node_SetPrivate(node, stack);
	Node_SetPreDestroyFunction(node, DestroyLOD);
}

/*currently static group use the same render as all grouping nodes, without any opts.
since we already do a lot of caching (lights, sensors) at the grouping node level, I'm not sure
we could get anything better trying to optimize the rest, since what is not cached in the base grouping
node always involves frustum culling, and caching that would require caching partial model matrix (from
the static group to each leaf)*/
#if 0
static void RenderStaticGroup(SFNode *node, void *rs)
{
	GroupingNode *group = (GroupingNode *) Node_GetPrivate(node);
	grouping_traverse(group, (RenderEffect3D*)rs, NULL);
}
#endif

void R3D_InitStaticGroup(Render3D *sr, SFNode *node)
{
	GroupingNode *stack = malloc(sizeof(GroupingNode));
	SetupGroupingNode(stack, sr->compositor, node, ((X_StaticGroup *)node)->children);
	Node_SetPrivate(node, stack);
	Node_SetPreDestroyFunction(node, DestroyBaseGrouping);
	Node_SetRenderFunction(node, RenderGroup);
}
