/*
 *			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 "visual_surface.h"


/*
		Shape 
*/
static void RenderShape(SFNode *node, void *rs)
{
	DrawableStack *st;
	u32 cull_bckup;
	RenderEffect3D *eff; 
	M_Shape *shape;
	
	eff = (RenderEffect3D *) rs;
	if (eff->traversing_mode==TRAVERSE_LIGHTING) return;

	shape = (M_Shape *) node;
	if (!shape->geometry) return;
	eff->appear = (SFNode *) shape->appearance;

	/*reset this node dirty flag (because bitmap may trigger bounds invalidation on the fly)*/
	Node_ClearDirty(node);

	/*check traverse mode, and take care of switch-off flag*/
	if (eff->traversing_mode==TRAVERSE_GET_BOUNDS) {
		/*this is done regardless of switch flag*/
		Node_Render((SFNode *) shape->geometry, eff);
		/*reset appearance dirty flag to get sure size changes in textures invalidates the parent graph(s)*/
		Node_ClearDirty(eff->appear);
		eff->appear = NULL;
	} else {
		if (eff->trav_flags & TF_SWITCHED_OFF) return;
		/*if we're here we passed culler already*/
		if (eff->traversing_mode==TRAVERSE_RENDER) 
			Node_Render((SFNode *) shape->geometry, eff);
		else if (eff->traversing_mode==TRAVERSE_SORT) {
			st = Node_GetPrivate(shape->geometry);
			cull_bckup = eff->cull_flag;
			if (st && node_cull(eff, &st->mesh->bounds, 0)) {
				VS_RegisterContext(eff, node, &st->mesh->bounds, 1);
			}
			eff->cull_flag = cull_bckup;
		}
		else if (eff->traversing_mode==TRAVERSE_PICK) drawable_do_pick(shape->geometry, eff);
		else if (eff->traversing_mode==TRAVERSE_COLLIDE) drawable_do_collide(shape->geometry, eff);

	}
}


Bool R3D_Get2DPlaneIntersection(M4Ray *ray, SFVec3f *res)
{
	M4Plane p;
	Fixed t, t2;
	p.normal.x = p.normal.y = 0; p.normal.z = FIX_ONE;
	p.d = 0;
	t2 = m4_vec_dot(p.normal, ray->dir);
	if (t2 == 0) return 0;
	t = - m4_divfix(m4_vec_dot(p.normal, ray->orig) + p.d, t2);
	if (t<0) return 0;
	*res = m4_vec_scale(ray->dir, t);
	m4_vec_add(*res, ray->orig, *res);
	return 1;
}

Bool R3D_PickInClipper(RenderEffect3D *eff, M4Rect *clip)
{
	SFVec3f pos;
	M4Matrix mx;
	M4Ray r;
	m4_mx_copy(mx, eff->model_matrix);
	m4_mx_inverse(&mx);
	r = eff->ray;
	m4_mx_apply_ray(&mx, &r);
	if (!R3D_Get2DPlaneIntersection(&r, &pos)) return 0;
	if ( (pos.x < clip->x) || (pos.y > clip->y) 
		|| (pos.x > clip->x + clip->width) || (pos.y < clip->y - clip->height) ) return 0;
	return 1;
}


void R3D_InitShape(Render3D *sr, SFNode *node)
{
	Node_SetRenderFunction(node, RenderShape);
}

static Bool R3D_NoIntersectionWithRay(SFNode *owner, M4Ray *ray, SFVec3f *vec, SFVec3f *outNormal, SFVec2f *outTexCoords)
{
	return 0;
}

static void RenderBox(SFNode *n, void *rs)
{
	RenderEffect3D *eff = (RenderEffect3D *)rs;
	DrawableStack *st = Node_GetPrivate(n);
	M_Box *box = (M_Box *)n;

	if (Node_GetDirty(n)) {
		mesh_new_box(st->mesh, box->size);
		Node_ClearDirty(n);
	}

	if (!eff->traversing_mode) {
		VS_DrawMesh(eff, st->mesh);
	}
	else if (eff->traversing_mode==TRAVERSE_GET_BOUNDS) {
		eff->bbox = st->mesh->bounds;
	}
}

void R3D_InitBox(Render3D *sr, SFNode *node)
{
	BaseDrawableStack(sr->compositor, node);
	Node_SetRenderFunction(node, RenderBox);
}

static void RenderCone(SFNode *n, void *rs)
{
	RenderEffect3D *eff = (RenderEffect3D *)rs;
	DrawableStack *st = Node_GetPrivate(n);
	M_Cone *co = (M_Cone *)n;

	if (Node_GetDirty(n)) {
		mesh_new_cone(st->mesh, co->height, co->bottomRadius, co->bottom, co->side, eff->surface->render->compositor->high_speed);
		Node_ClearDirty(n);
	}

	if (!eff->traversing_mode) {
		VS_DrawMesh(eff, st->mesh);
	}
	else if (eff->traversing_mode==TRAVERSE_GET_BOUNDS) {
		eff->bbox = st->mesh->bounds;
	}
}

void R3D_InitCone(Render3D *sr, SFNode *node)
{
	BaseDrawableStack(sr->compositor, node);
	Node_SetRenderFunction(node, RenderCone);
}

static void RenderCylinder(SFNode *n, void *rs)
{
	RenderEffect3D *eff = (RenderEffect3D *)rs;
	DrawableStack *st = Node_GetPrivate(n);
	M_Cylinder *cy = (M_Cylinder *)n;

	if (Node_GetDirty(n) ) {
		mesh_new_cylinder(st->mesh, cy->height, cy->radius, cy->bottom, cy->side, cy->top, eff->surface->render->compositor->high_speed);
		Node_ClearDirty(n);
	}

	if (!eff->traversing_mode) {
		VS_DrawMesh(eff, st->mesh);
	}
	else if (eff->traversing_mode==TRAVERSE_GET_BOUNDS) {
		eff->bbox = st->mesh->bounds;
	}
}

void R3D_InitCylinder(Render3D *sr, SFNode *node)
{
	BaseDrawableStack(sr->compositor, node);
	Node_SetRenderFunction(node, RenderCylinder);
}

static void RenderSphere(SFNode *n, void *rs)
{
	RenderEffect3D *eff = (RenderEffect3D *)rs;
	DrawableStack *st = Node_GetPrivate(n);
	M_Sphere *sp = (M_Sphere *)n;

	if (Node_GetDirty(n)) {
		mesh_new_sphere(st->mesh, sp->radius, eff->surface->render->compositor->high_speed);
		Node_ClearDirty(n);
	}

	if (!eff->traversing_mode) {
		VS_DrawMesh(eff, st->mesh);
	}
	else if (eff->traversing_mode==TRAVERSE_GET_BOUNDS) {
		eff->bbox = st->mesh->bounds;
	}
}

void R3D_InitSphere(Render3D *sr, SFNode *node)
{
	BaseDrawableStack(sr->compositor, node);
	Node_SetRenderFunction(node, RenderSphere);
}

static Bool CircleIntersectWithRay(SFNode *owner, M4Ray *ray, SFVec3f *outPoint, SFVec3f *outNormal, SFVec2f *outTexCoords)
{
	Fixed r, sqr;
	Bool inside;
	if (!R3D_Get2DPlaneIntersection(ray, outPoint)) return 0;
	r = ((M_Circle *)owner)->radius;
	sqr = m4_mulfix(r, r);
	inside = (sqr >= m4_mulfix(outPoint->x, outPoint->x) + m4_mulfix(outPoint->y, outPoint->y)) ? 1 : 0;
	if (!inside) return 0;
	if (outNormal) { outNormal->x = outNormal->y = 0; outNormal->z = FIX_ONE; }
	if (outTexCoords) { 
		outTexCoords->x = m4_divfix(outPoint->x, r) + FIX_ONE/2;
		outTexCoords->y = m4_divfix(outPoint->y, r) + FIX_ONE/2;
	}
	return 1;
}

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

	if (Node_GetDirty(node)) {
		Fixed a = ((M_Circle *) node)->radius * 2;
		stack2D_reset(st);
		m4_path_add_ellipse(st->path, 0, 0, a, a);
		mesh_new_ellipse(st->mesh, a, a, eff->surface->render->compositor->high_speed);
		Node_ClearDirty(node);
	}
	if (!eff->traversing_mode) {
		stack2D_draw(st, eff);
	} else if (eff->traversing_mode==TRAVERSE_GET_BOUNDS) 
		eff->bbox = st->mesh->bounds;
}

void R3D_InitCircle(Render3D *sr, SFNode *node)
{
	stack2D *st = BaseStack2D(sr->compositor, node);
	Node_SetRenderFunction(node, RenderCircle);
	st->IntersectWithRay = CircleIntersectWithRay;
}

static Bool EllipseIntersectWithRay(SFNode *owner, M4Ray *ray, SFVec3f *outPoint, SFVec3f *outNormal, SFVec2f *outTexCoords)
{
	Fixed a, b;
	Bool inside;
	if (!R3D_Get2DPlaneIntersection(ray, outPoint)) return 0;
	a = ((M_Ellipse *)owner)->radius.x; a = m4_mulfix(a, a);
	b = ((M_Ellipse *)owner)->radius.y; b = m4_mulfix(b, b);
	inside = (m4_muldiv(outPoint->x, outPoint->x, a) + m4_muldiv(outPoint->y, outPoint->y, b) <= 1) ? 1 : 0;
	if (!inside) return 0;
	if (outNormal) { outNormal->x = outNormal->y = 0; outNormal->z = FIX_ONE; }
	if (outTexCoords) { 
		outTexCoords->x = m4_divfix(outPoint->x, a) + FIX_ONE/2;
		outTexCoords->y = m4_divfix(outPoint->y, b) + FIX_ONE/2;
	}
	return 1;
}

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

	if (Node_GetDirty(node)) {
		Fixed a = ((M_Ellipse *) node)->radius.x;
		Fixed b = ((M_Ellipse *) node)->radius.y;
		stack2D_reset(st);
		m4_path_add_ellipse(st->path, 0, 0, a, b);
		mesh_new_ellipse(st->mesh, a, b, eff->surface->render->compositor->high_speed);
		Node_ClearDirty(node);
	}
	if (!eff->traversing_mode) 
		stack2D_draw(st, eff);
	else if (eff->traversing_mode==TRAVERSE_GET_BOUNDS) 
		eff->bbox = st->mesh->bounds;
}

void R3D_InitEllipse(Render3D *sr, SFNode *node)
{
	stack2D *st = BaseStack2D(sr->compositor, node);
	Node_SetRenderFunction(node, RenderEllipse);
	st->IntersectWithRay = EllipseIntersectWithRay;
}

static Bool RectangleIntersectWithRay(SFNode *owner, M4Ray *ray, SFVec3f *outPoint, SFVec3f *outNormal, SFVec2f *outTexCoords)
{
	stack2D *st;
	Bool inside;
	if (!R3D_Get2DPlaneIntersection(ray, outPoint)) return 0;
	st = (stack2D *) Node_GetPrivate(owner);
	inside = ( (outPoint->x >= st->mesh->bounds.min_edge.x) && (outPoint->y >= st->mesh->bounds.min_edge.y) 
			&& (outPoint->x <= st->mesh->bounds.max_edge.x) && (outPoint->y <= st->mesh->bounds.max_edge.y) 
			) ? 1 : 0;

	if (!inside) return 0;
	if (outNormal) { outNormal->x = outNormal->y = 0; outNormal->z = FIX_ONE; }
	if (outTexCoords) { 
		outTexCoords->x = m4_divfix(outPoint->x, ((M_Rectangle*)owner)->size.x) + FIX_ONE/2;
		outTexCoords->y = m4_divfix(outPoint->y, ((M_Rectangle*)owner)->size.y) + FIX_ONE/2;
	}
	return 1;
}

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

	/*build vec path*/
	if (Node_GetDirty(node)) {
		SFVec2f s = ((M_Rectangle *) node)->size;
		stack2D_reset(st);
		m4_path_add_rect_center(st->path, 0, 0, s.x, s.y);
		mesh_new_rectangle(st->mesh, s);
		Node_ClearDirty(node);
	}
	if (!eff->traversing_mode) 
		stack2D_draw(st, eff);
	else if (eff->traversing_mode==TRAVERSE_GET_BOUNDS) 
		eff->bbox = st->mesh->bounds;
}

void R3D_InitRectangle(Render3D *sr, SFNode *node)
{
	stack2D *st = BaseStack2D(sr->compositor, node);
	Node_SetRenderFunction(node, RenderRectangle);
	st->IntersectWithRay = RectangleIntersectWithRay;
}


Bool Stack2DIntersectWithRay(SFNode *owner, M4Ray *r, SFVec3f *outPoint, SFVec3f *outNormal, SFVec2f *outTexCoords)
{
	Bool inside;
	stack2D *st;
	if (!R3D_Get2DPlaneIntersection(r, outPoint)) return 0;
	st = (stack2D *) Node_GetPrivate(owner);

	if ( (outPoint->x < st->mesh->bounds.min_edge.x) || (outPoint->y < st->mesh->bounds.min_edge.y) 
			|| (outPoint->x > st->mesh->bounds.max_edge.x) || (outPoint->y > st->mesh->bounds.max_edge.y) ) return 0;

	inside = m4_path_point_over(st->path, outPoint->x, outPoint->y);
	if (!inside) return 0;
	if (outNormal) { outNormal->x = outNormal->y = 0; outNormal->z = FIX_ONE; }
	if (outTexCoords) { 
		outTexCoords->x = m4_divfix(outPoint->x, (st->mesh->bounds.max_edge.x - st->mesh->bounds.min_edge.x)) + FIX_ONE/2;
		outTexCoords->y = m4_divfix(outPoint->y, (st->mesh->bounds.max_edge.y - st->mesh->bounds.min_edge.y)) + FIX_ONE/2;
	}
	return 1;
}


//#define CHECK_VALID_C2D(nbPts) if (cur_index+nbPts>=pt_count) { m4_path_reset(st->path); return; }
#define CHECK_VALID_C2D(nbPts)

static void build_curve2D(stack2D *st, M_Curve2D *c2D)
{
	SFVec2f orig, ct_orig, ct_end, end;
	u32 cur_index, i, remain, type_count, pt_count;
	SFVec2f *pts;
	M_Coordinate2D *coord = (M_Coordinate2D *)c2D->point;

	pts = coord->point.vals;
	if (!pts) 
		return;

	cur_index = c2D->type.count ? 1 : 0;
	/*if the first type is a moveTo skip initial moveTo*/
	i=0;
	if (cur_index) {
		while (c2D->type.vals[i]==0) i++;
	}
	ct_orig = orig = pts[i];

	m4_path_add_move_to(st->path, orig.x, orig.y);

	pt_count = coord->point.count;
	type_count = c2D->type.count;
	for (; i<type_count; i++) {

		switch (c2D->type.vals[i]) {
		/*moveTo, 1 point*/
		case 0:
			CHECK_VALID_C2D(0);
			orig = pts[cur_index];
			if (i) m4_path_add_move_to(st->path, orig.x, orig.y);
			cur_index += 1;
			break;
		/*lineTo, 1 point*/
		case 1:
			CHECK_VALID_C2D(0);
			end = pts[cur_index];
			m4_path_add_line_to(st->path, end.x, end.y);
			orig = end;
			cur_index += 1;
			break;
		/*curveTo, 3 points*/
		case 2:
			CHECK_VALID_C2D(2);
			ct_orig = pts[cur_index];
			ct_end = pts[cur_index+1];
			end = pts[cur_index+2];
			m4_path_add_cubic_to(st->path, ct_orig.x, ct_orig.y, ct_end.x, ct_end.y, end.x, end.y);
			cur_index += 3;
			ct_orig = ct_end;
			orig = end;
			break;
		/*nextCurveTo, 2 points (cf spec)*/
		case 3:
			CHECK_VALID_C2D(1);
			ct_orig.x = 2*orig.x - ct_orig.x;
			ct_orig.y = 2*orig.y - ct_orig.y;
			ct_end = pts[cur_index];
			end = pts[cur_index+1];
			m4_path_add_cubic_to(st->path, ct_orig.x, ct_orig.y, ct_end.x, ct_end.y, end.x, end.y);
			cur_index += 2;
			ct_orig = ct_end;
			orig = end;
			break;

		/*all XCurve2D specific*/

		/*CW and CCW ArcTo*/
		case 4:
		case 5:
			CHECK_VALID_C2D(2);
			ct_orig = pts[cur_index];
			ct_end = pts[cur_index+1];
			end = pts[cur_index+2];
			m4_path_add_arc_to(st->path, end.x, end.y, ct_orig.x, ct_orig.y, ct_end.x, ct_end.y, (c2D->type.vals[i]==5) ? 1 : 0);
			cur_index += 3;
			ct_orig = ct_end;
			orig = end;
			break;
		/*ClosePath*/
		case 6:
			m4_path_close(st->path);
			break;
		/*quadratic CurveTo, 2 points*/
		case 7:
			CHECK_VALID_C2D(1);
			ct_end = pts[cur_index];
			end = pts[cur_index+1];
			m4_path_add_quadratic_to(st->path, ct_end.x, ct_end.y, end.x, end.y);
			cur_index += 2;
			ct_orig = ct_end;
			orig = end;
			break;
		}
	}

	/*what's left is an N-bezier spline*/
	if (pt_count > cur_index) {
		/*first moveto*/
		if (!cur_index) cur_index++;

		remain = pt_count - cur_index;

		if (remain>1)
			m4_path_add_bezier(st->path, &pts[cur_index], remain);
	}
}

static void RenderCurve2D(SFNode *node, void *rs)
{
	M_Curve2D *c2D = (M_Curve2D *)node;
	stack2D *st = Node_GetPrivate(node);
	RenderEffect3D *eff = rs;

	if (!c2D->point) return;

	if (Node_GetDirty(node)) {
		stack2D_reset(st);
		st->path->fineness = c2D->fineness;
		if (st->compositor->high_speed) st->path->fineness /= 2;
		build_curve2D(st, c2D);
		mesh_from_path(st->mesh, st->path);
		Node_ClearDirty(node);
	}
	if (!eff->traversing_mode) 
		stack2D_draw(st, eff);
	else if (eff->traversing_mode==TRAVERSE_GET_BOUNDS) 
		eff->bbox = st->mesh->bounds;
}

void R3D_InitCurve2D(Render3D *sr, SFNode *node)
{
	stack2D *st = BaseStack2D(sr->compositor, node);
	Node_SetRenderFunction(node, RenderCurve2D);
	st->IntersectWithRay = Stack2DIntersectWithRay;
}

static void build_ils2d(stack2D *st, M_IndexedLineSet2D *ils2D)
{
	u32 i;
	Bool started;
	SFVec2f *pts;
	M_Coordinate2D *coord = (M_Coordinate2D *)ils2D->coord;

	pts = coord->point.vals;
	if (ils2D->coordIndex.count > 0) {
		started = 0;
		for (i=0; i < ils2D->coordIndex.count; i++) {
			/*NO close on ILS2D*/
			if (ils2D->coordIndex.vals[i] == -1) {
				started = 0;
			} else if (!started) {
				started = 1;
				m4_path_add_move_to(st->path, pts[ils2D->coordIndex.vals[i]].x, pts[ils2D->coordIndex.vals[i]].y);
			} else {
				m4_path_add_line_to(st->path, pts[ils2D->coordIndex.vals[i]].x, pts[ils2D->coordIndex.vals[i]].y);
			}
		}
	} else if (coord->point.count) {
		m4_path_add_move_to(st->path, pts[0].x, pts[0].y);
		for (i=1; i < coord->point.count; i++) {
			m4_path_add_line_to(st->path, pts[i].x, pts[i].y);
		}
	}
}

static void RenderILS2D(SFNode *node, void *rs)
{
	Aspect2D asp;
	StrikeInfo *si;
	M_IndexedLineSet2D *ils2D = (M_IndexedLineSet2D *)node;
	stack2D *st = Node_GetPrivate(node);
	RenderEffect3D *eff = rs;

	if (Node_GetDirty(node)) {
		stack2D_reset(st);
		build_ils2d(st, ils2D);
		mesh_new_ils(st->mesh, ils2D->coord, &ils2D->coordIndex, ils2D->color, &ils2D->colorIndex, ils2D->colorPerVertex, 0);
		Node_ClearDirty(node);
	}

	VS_GetAspect2D(eff, &asp);

	if (!eff->traversing_mode) {
		VS3D_SetAntiAlias(eff->surface, (eff->surface->render->compositor->antiAlias==M4_AL_All) ? 1 : 0);
		if (ils2D->color) {
			VS3D_StrikeMesh(eff, st->mesh, Aspect_GetLineWidth(&asp), asp.pen_props.dash);
		} else {
			si = VS_GetStrikeInfo(st, &asp, eff);
			if (si) {
				VS_Set2DStrikeAspect(eff->surface, &asp);
				if (!si->is_vectorial) {
					VS3D_StrikeMesh(eff, si->outline, Aspect_GetLineWidth(&asp), asp.pen_props.dash);
				} else {
					VS3D_DrawMesh(eff, si->outline, 0);
				}
			}
		}
	} else if (eff->traversing_mode==TRAVERSE_GET_BOUNDS) {
		eff->bbox = st->mesh->bounds;
	}
	
}

static void ILS2D_SetColorIndex(SFNode *node)
{
	M_IndexedLineSet2D *ils2D = (M_IndexedLineSet2D *)node;
	VRML_FieldCopy(&ils2D->colorIndex, &ils2D->set_colorIndex, FT_MFInt32);
}

static void ILS2D_SetCoordIndex(SFNode *node)
{
	M_IndexedLineSet2D *ils2D = (M_IndexedLineSet2D *)node;
	VRML_FieldCopy(&ils2D->coordIndex, &ils2D->set_coordIndex, FT_MFInt32);
}

void R3D_InitILS2D(Render3D *sr, SFNode *node)
{
	M_IndexedLineSet2D *ils2D = (M_IndexedLineSet2D *)node;
	stack2D *st = BaseStack2D(sr->compositor, node);
	Node_SetRenderFunction(node, RenderILS2D);
	ils2D->on_set_colorIndex = ILS2D_SetColorIndex;
	ils2D->on_set_coordIndex = ILS2D_SetCoordIndex;
	st->IntersectWithRay = R3D_NoIntersectionWithRay;
}

static void RenderPointSet2D(SFNode *node, void *rs)
{
	M_PointSet2D *ps2D = (M_PointSet2D *)node;
	DrawableStack *st = Node_GetPrivate(node);
	RenderEffect3D *eff = rs;
	if (!ps2D->coord) return;

	if (Node_GetDirty(node)) {
		mesh_new_ps(st->mesh, ps2D->coord, ps2D->color);
		Node_ClearDirty(node);
	}
	
	if (!eff->traversing_mode) {
		Aspect2D asp;
		VS_GetAspect2D(eff, &asp);
		VS3D_SetMaterial2D(eff->surface, asp.fill_color, asp.alpha);
		VS3D_DrawMesh(eff, st->mesh, 0);
	}
	else if (eff->traversing_mode==TRAVERSE_GET_BOUNDS) {
		eff->bbox = st->mesh->bounds;
	}
}


void R3D_InitPointSet2D(Render3D *sr, SFNode *node)
{
	DrawableStack *st = BaseDrawableStack(sr->compositor, node);
	Node_SetRenderFunction(node, RenderPointSet2D);
	st->IntersectWithRay = R3D_NoIntersectionWithRay;
}

static void build_ifs2d(stack2D *st, M_IndexedFaceSet2D *ifs2D)
{
	u32 i;
	SFVec2f *pts;
	u32 ci_count, c_count;
	Bool started;
	M_Coordinate2D *coord = (M_Coordinate2D *)ifs2D->coord;

	c_count = coord->point.count;
	ci_count = ifs2D->coordIndex.count;
	pts = coord->point.vals;

	if (ci_count > 0) {
		started = 0;
		for (i=0; i < ci_count; i++) {
			if (ifs2D->coordIndex.vals[i] == -1) {
				m4_path_close(st->path);
				started = 0;
			} else if (!started) {
				started = 1;
				m4_path_add_move_to(st->path, pts[ifs2D->coordIndex.vals[i]].x, pts[ifs2D->coordIndex.vals[i]].y);
			} else {
				m4_path_add_line_to(st->path, pts[ifs2D->coordIndex.vals[i]].x, pts[ifs2D->coordIndex.vals[i]].y);
			}
		}
		if (started) m4_path_close(st->path);
	} else if (c_count) {
		m4_path_add_move_to(st->path, pts[0].x, pts[0].y);
		for (i=1; i < c_count; i++) {
			m4_path_add_line_to(st->path, pts[i].x, pts[i].y);
		}
		m4_path_close(st->path);
	}
}

static void RenderIFS2D(SFNode *node, void *rs)
{
	Aspect2D asp;
	StrikeInfo *si;
	M_IndexedFaceSet2D *ifs2D = (M_IndexedFaceSet2D *)node;
	stack2D *st = Node_GetPrivate(node);
	RenderEffect3D *eff = rs;

	if (Node_GetDirty(node)) {
		stack2D_reset(st);
		build_ifs2d(st, ifs2D);
		mesh_new_ifs2d(st->mesh, node);
		Node_ClearDirty(node);
	}
	VS_GetAspect2D(eff, &asp);

	if (!eff->traversing_mode) {
		if (ifs2D->color && !asp.filled) {
			/*use special func to disable outline recompute and handle recompute ourselves*/
			si = VS_GetStrikeInfoIFS(st, &asp, eff);
			if (!si->outline) {
				si->outline = new_mesh();
				mesh_new_ils(si->outline, ifs2D->coord, &ifs2D->coordIndex, ifs2D->color, &ifs2D->colorIndex, ifs2D->colorPerVertex, 1);
			}
			VS3D_StrikeMesh(eff, si->outline, Aspect_GetLineWidth(&asp), asp.pen_props.dash);
		} else {
			stack2D_draw(st, eff);
		}
	} 
	else if (eff->traversing_mode==TRAVERSE_GET_BOUNDS) {
		eff->bbox = st->mesh->bounds;
	}
}

static void IFS2D_SetColorIndex(SFNode *node)
{
	M_IndexedFaceSet2D *ifs2D = (M_IndexedFaceSet2D *)node;
	VRML_FieldCopy(&ifs2D->colorIndex, &ifs2D->set_colorIndex, FT_MFInt32);
}

static void IFS2D_SetCoordIndex(SFNode *node)
{
	M_IndexedFaceSet2D *ifs2D = (M_IndexedFaceSet2D *)node;
	VRML_FieldCopy(&ifs2D->coordIndex, &ifs2D->set_coordIndex, FT_MFInt32);
}

static void IFS2D_SetTexCoordIndex(SFNode *node)
{
	M_IndexedFaceSet2D *ifs2D = (M_IndexedFaceSet2D *)node;
	VRML_FieldCopy(&ifs2D->texCoordIndex, &ifs2D->set_texCoordIndex, FT_MFInt32);
}

void R3D_InitIFS2D(Render3D *sr, SFNode *node)
{
	M_IndexedFaceSet2D *ifs2D = (M_IndexedFaceSet2D *)node;
	stack2D *st = BaseStack2D(sr->compositor, node);
	Node_SetRenderFunction(node, RenderIFS2D);
	ifs2D->on_set_colorIndex = IFS2D_SetColorIndex;
	ifs2D->on_set_coordIndex = IFS2D_SetCoordIndex;
	ifs2D->on_set_texCoordIndex = IFS2D_SetTexCoordIndex;
	st->IntersectWithRay = Stack2DIntersectWithRay;
}

static void RenderPointSet(SFNode *node, void *rs)
{
	M_PointSet *ps = (M_PointSet *)node;
	DrawableStack *st = Node_GetPrivate(node);
	RenderEffect3D *eff = rs;
	if (!ps->coord) return;

	if (Node_GetDirty(node)) {
		mesh_new_ps(st->mesh, ps->coord, ps->color);
		Node_ClearDirty(node);
	}
	
	if (!eff->traversing_mode) {
		VS_DrawMesh(eff, st->mesh);
	}
	else if (eff->traversing_mode==TRAVERSE_GET_BOUNDS) {
		eff->bbox = st->mesh->bounds;
	}
}

void R3D_InitPointSet(Render3D *sr, SFNode *node)
{
	DrawableStack *st = BaseDrawableStack(sr->compositor, node);
	Node_SetRenderFunction(node, RenderPointSet);
	st->IntersectWithRay = R3D_NoIntersectionWithRay;
}

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

	if (Node_GetDirty(node)) {
		mesh_new_ifs(st->mesh, node);
		Node_ClearDirty(node);
	}
	if (!eff->traversing_mode) {
		VS_DrawMesh(eff, st->mesh);
	} else if (eff->traversing_mode==TRAVERSE_GET_BOUNDS) {
		eff->bbox = st->mesh->bounds;
	}
}

static void IFS_SetColorIndex(SFNode *node)
{
	M_IndexedFaceSet *ifs = (M_IndexedFaceSet *)node;
	VRML_FieldCopy(&ifs->colorIndex, &ifs->set_colorIndex, FT_MFInt32);
}

static void IFS_SetCoordIndex(SFNode *node)
{
	M_IndexedFaceSet *ifs = (M_IndexedFaceSet *)node;
	VRML_FieldCopy(&ifs->coordIndex, &ifs->set_coordIndex, FT_MFInt32);
}

static void IFS_SetNormalIndex(SFNode *node)
{
	M_IndexedFaceSet *ifs = (M_IndexedFaceSet *)node;
	VRML_FieldCopy(&ifs->normalIndex, &ifs->set_normalIndex, FT_MFInt32);
}

static void IFS_SetTexCoordIndex(SFNode *node)
{
	M_IndexedFaceSet *ifs = (M_IndexedFaceSet *)node;
	VRML_FieldCopy(&ifs->texCoordIndex, &ifs->set_texCoordIndex, FT_MFInt32);
}

void R3D_InitIFS(Render3D *sr, SFNode *node)
{
	M_IndexedFaceSet *ifs = (M_IndexedFaceSet *)node;
	BaseDrawableStack(sr->compositor, node);
	Node_SetRenderFunction(node, RenderIFS);
	ifs->on_set_colorIndex = IFS_SetColorIndex;
	ifs->on_set_coordIndex = IFS_SetCoordIndex;
	ifs->on_set_normalIndex = IFS_SetNormalIndex;
	ifs->on_set_texCoordIndex = IFS_SetTexCoordIndex;
}

static void RenderILS(SFNode *node, void *rs)
{
	M_IndexedLineSet *ils = (M_IndexedLineSet *)node;
	DrawableStack *st = Node_GetPrivate(node);
	RenderEffect3D *eff = rs;

	if (!ils->coord) return;

	if (Node_GetDirty(node)) {
		mesh_new_ils(st->mesh, ils->coord, &ils->coordIndex, ils->color, &ils->colorIndex, ils->colorPerVertex, 0);
		Node_ClearDirty(node);
	}
	if (!eff->traversing_mode) {
		VS3D_SetAntiAlias(eff->surface, (eff->surface->render->compositor->antiAlias==M4_AL_All) ? 1 : 0);
		VS_DrawMesh(eff, st->mesh);
	} else if (eff->traversing_mode==TRAVERSE_GET_BOUNDS) {
		eff->bbox = st->mesh->bounds;
	}
}

static void ILS_SetColorIndex(SFNode *node)
{
	M_IndexedLineSet *ils = (M_IndexedLineSet *)node;
	VRML_FieldCopy(&ils->colorIndex, &ils->set_colorIndex, FT_MFInt32);
}

static void ILS_SetCoordIndex(SFNode *node)
{
	M_IndexedLineSet *ils = (M_IndexedLineSet *)node;
	VRML_FieldCopy(&ils->coordIndex, &ils->set_coordIndex, FT_MFInt32);
}

void R3D_InitILS(Render3D *sr, SFNode *node)
{
	M_IndexedLineSet *ils = (M_IndexedLineSet *)node;
	DrawableStack *st = BaseDrawableStack(sr->compositor, node);
	Node_SetRenderFunction(node, RenderILS);
	ils->on_set_colorIndex = ILS_SetColorIndex;
	ils->on_set_coordIndex = ILS_SetCoordIndex;
	st->IntersectWithRay = R3D_NoIntersectionWithRay;
}

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

	if (Node_GetDirty(node)) {
		mesh_new_elevation_grid(st->mesh, node);
		Node_ClearDirty(node);
	}
	if (!eff->traversing_mode) {
		VS_DrawMesh(eff, st->mesh);
	} else if (eff->traversing_mode==TRAVERSE_GET_BOUNDS) {
		eff->bbox = st->mesh->bounds;
	}
}

static void ElevationGrid_SetHeight(SFNode *node)
{
	M_ElevationGrid *eg = (M_ElevationGrid *)node;
	VRML_FieldCopy(&eg->height, &eg->set_height, FT_MFFloat);
}

void R3D_InitElevationGrid(Render3D *sr, SFNode *node)
{
	M_ElevationGrid *eg = (M_ElevationGrid *)node;
	BaseDrawableStack(sr->compositor, node);
	Node_SetRenderFunction(node, RenderElevationGrid);
	eg->on_set_height = ElevationGrid_SetHeight;
}


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

	if (Node_GetDirty(node)) {
		mesh_new_extrusion(st->mesh, node);
		Node_ClearDirty(node);
	}
	if (!eff->traversing_mode) {
		VS_DrawMesh(eff, st->mesh);
	} else if (eff->traversing_mode==TRAVERSE_GET_BOUNDS) {
		eff->bbox = st->mesh->bounds;
	}
}

static void Extrusion_SetCrossSection(SFNode *node)
{
	M_Extrusion *eg = (M_Extrusion *)node;
	VRML_FieldCopy(&eg->crossSection, &eg->set_crossSection, FT_MFVec2f);
}
static void Extrusion_SetOrientation(SFNode *node)
{
	M_Extrusion *eg = (M_Extrusion *)node;
	VRML_FieldCopy(&eg->orientation, &eg->set_orientation, FT_MFRotation);
}
static void Extrusion_SetScale(SFNode *node)
{
	M_Extrusion *eg = (M_Extrusion *)node;
	VRML_FieldCopy(&eg->scale, &eg->set_scale, FT_MFVec2f);
}
static void Extrusion_SetSpine(SFNode *node)
{
	M_Extrusion *eg = (M_Extrusion *)node;
	VRML_FieldCopy(&eg->spine, &eg->set_spine, FT_MFVec3f);
}
void R3D_InitExtrusion(Render3D *sr, SFNode *node)
{
	M_Extrusion *ext = (M_Extrusion *)node;
	BaseDrawableStack(sr->compositor, node);
	Node_SetRenderFunction(node, RenderExtrusion);
	ext->on_set_crossSection = Extrusion_SetCrossSection;
	ext->on_set_orientation = Extrusion_SetOrientation;
	ext->on_set_scale = Extrusion_SetScale;
	ext->on_set_spine = Extrusion_SetSpine;
}


/*
			NonLinearDeformer

	NOTE: AFX spec is just hmm, well, hmm. NonLinearDeformer.extend interpretation differ from type to 
	type within the spec, and between the spec and the ref soft. This is GPAC interpretation
	* all params are specified with default transform axis (Z axis)
	* NLD.type = 0 (taper):
		* taping radius = NLD.param
		* extend = N * [diff, perc] with 
			- diff : relative position along taper axis (for default, diff=0: z min, diff=1: z max)
			- perc: mult ratio for base taper radius
		extend works like key/keyValue for a scalar interpolator
		final radius: r(z) = LinearInterp(extend[min key], extend[min key + 1]) * param

	* NLD.type = 1 (twister):
		* twisting angle = NLD.param
		* extend = N * [diff, perc] with 
			- diff : relative position along twister axis (for default, diff=0: z min, diff=1: z max)
			- perc: mult ratio for base twister angle
		extend works like key/keyValue for a scalar interpolator
		final angle: a(z) = LinearInterp(extend[min key], extend[min key + 1]) * param

	* NLD.type = 2 (bender):
		* bending curvature = NLD.param
		* extend = N * [diff, perc] with 
			- diff : relative position along bender axis (for default, diff=0: z min, diff=1: z max)
			- perc: mult ratio for base bender curvature
		extend works like key/keyValue for a scalar interpolator
		final curvature: c(z) = LinearInterp(extend[min key], extend[min key + 1]) * param

  Another pb of NLD is that the spec says nothing about object/axis alignment: should we center
  the object at 0,0,0 (local coords) or not? the results are quite different. Here we don't 
  recenter the object before transform
*/

static Bool NLD_GetMatrix(M_NonLinearDeformer *nld, M4Matrix *mx)
{
	SFVec3f v1, v2;
	SFRotation r;
	Fixed l1, l2, dot;

	/*compute rotation matrix from NLD axis to 0 0 1*/
	v1 = nld->axis;
	m4_vec_norm(&v1);
	v2.x = v2.y = 0; v2.z = 1;
	if (m4_vec_equal(v1, v2)) return 0;

	l1 = m4_vec_len(v1);
	l2 = m4_vec_len(v2);
	dot = m4_divfix(m4_vec_dot(v1, v2), m4_mulfix(l1, l2));

	r.x = m4_mulfix(v1.y, v2.z) - m4_mulfix(v2.y, v1.z);
	r.y = m4_mulfix(v1.z, v2.x) - m4_mulfix(v2.z, v1.x);
	r.z = m4_mulfix(v1.x, v2.y) - m4_mulfix(v2.x, v1.y);
	r.q = m4_atan2(dot, m4_sqrt(FIX_ONE - m4_mulfix(dot, dot)));
	m4_mx_init(*mx);
	m4_mx_add_rotation(mx, r.q, r.x, r.y, r.z);
	return 1;
}

static M4INLINE void NLD_GetKey(M_NonLinearDeformer *nld, Fixed frac, Fixed *f_min, Fixed *min, Fixed *f_max, Fixed *max)
{
	u32 i, count;
	count = nld->extend.count;
	if (count%2) count--;

	*f_min = 0;
	*min = 0;
	for (i=0; i<count; i+=2) {
		if (frac>=nld->extend.vals[i]) {
			*f_min = nld->extend.vals[i];
			*min = nld->extend.vals[i+1];
		} 
		if ((i+2<count) && (frac<=nld->extend.vals[i+2])) {
			*f_max = nld->extend.vals[i+2];
			*max = nld->extend.vals[i+3];
			return;
		}
	}
	if (count) {
		*f_max = nld->extend.vals[count-2];
		*max = nld->extend.vals[count-1];
	} else {
		*f_max = FIX_ONE;
		*max = M4_PI;
	}
}

static void NLD_Apply(M_NonLinearDeformer *nld, M4Mesh *mesh)
{
	u32 i;
	M4Matrix mx;
	Fixed param, z_min, z_max, v_min, v_max, f_min, f_max, frac, val, a_cos, a_sin;
	Bool needs_transform = NLD_GetMatrix(nld, &mx);

	param = nld->param;
	if (!param) param = 1;
	
	if (mesh->bounds.min_edge.z == mesh->bounds.max_edge.z) return;

	z_min = FIX_MAX;
	z_max = -FIX_MAX;
	if (needs_transform) {
		for (i=0; i<mesh->v_count; i++) {
			m4_mx_apply_vec(&mx, &mesh->vertices[i].pos);
			m4_mx_rotate_vector(&mx, &mesh->vertices[i].normal);
			if (mesh->vertices[i].pos.z<z_min) z_min = mesh->vertices[i].pos.z;
			if (mesh->vertices[i].pos.z>z_max) z_max = mesh->vertices[i].pos.z;
		}
	} else {
		z_min = mesh->bounds.min_edge.z;
		z_max = mesh->bounds.max_edge.z;
	}
	
	for (i=0; i<mesh->v_count; i++) {
		SFVec3f old = mesh->vertices[i].pos;
		frac = m4_divfix(old.z - z_min, z_max - z_min);
		NLD_GetKey(nld, frac, &f_min, &v_min, &f_max, &v_max);
		if (f_max == f_min) {
			val = v_min;
		} else {
			frac = m4_divfix(frac-f_min, f_max - f_min);
			val = m4_mulfix(v_max - v_min, frac) + v_min;
		}
		val = m4_mulfix(val, param);

		switch (nld->type) {
		/*taper*/
		case 0:
			mesh->vertices[i].pos.x = m4_mulfix(mesh->vertices[i].pos.x, val);
			mesh->vertices[i].pos.y = m4_mulfix(mesh->vertices[i].pos.y, val);
			old = mesh->vertices[i].normal;
			mesh->vertices[i].normal.x = m4_mulfix(mesh->vertices[i].normal.x, val);
			mesh->vertices[i].normal.y = m4_mulfix(mesh->vertices[i].normal.y, val);
			m4_vec_norm(&mesh->vertices[i].normal);
			break;
		/*twist*/
		case 1:
			a_cos = m4_cos(val);
			a_sin = m4_sin(val);
			mesh->vertices[i].pos.x = m4_mulfix(a_cos, old.x) - m4_mulfix(a_sin, old.y);
			mesh->vertices[i].pos.y = m4_mulfix(a_sin, old.x) + m4_mulfix(a_cos, old.y);
			old = mesh->vertices[i].normal;
			mesh->vertices[i].normal.x = m4_mulfix(a_cos, old.x) -  m4_mulfix(a_sin, old.y);
			mesh->vertices[i].normal.y = m4_mulfix(a_sin, old.x) + m4_mulfix(a_cos, old.y);
			m4_vec_norm(&mesh->vertices[i].normal);
			break;
		/*bend*/
		case 2:
			a_cos = m4_cos(val);
			a_sin = m4_sin(val);
			mesh->vertices[i].pos.x = m4_mulfix(a_sin, old.z) + m4_mulfix(a_cos, old.x);
			mesh->vertices[i].pos.z = m4_mulfix(a_cos, old.z) - m4_mulfix(a_sin, old.x);
			old = mesh->vertices[i].normal;
			mesh->vertices[i].normal.x = m4_mulfix(a_sin, old.z) +  m4_mulfix(a_cos, old.x);
			mesh->vertices[i].normal.z = m4_mulfix(a_cos, old.z) - m4_mulfix(a_sin, old.x);
			m4_vec_norm(&mesh->vertices[i].normal);
			break;
		/*pinch, not standard  (taper on X dim only)*/
		case 3:
			mesh->vertices[i].pos.x = m4_mulfix(mesh->vertices[i].pos.x, val);
			old = mesh->vertices[i].normal;
			mesh->vertices[i].normal.x = m4_mulfix(mesh->vertices[i].normal.x, val);
			m4_vec_norm(&mesh->vertices[i].normal);
			break;
		}
	}
	if (needs_transform) {
		m4_mx_inverse(&mx);
		for (i=0; i<mesh->v_count; i++) {
			m4_mx_apply_vec(&mx, &mesh->vertices[i].pos);
			m4_mx_rotate_vector(&mx, &mesh->vertices[i].normal);
		}
	}
	mesh_update_bounds(mesh);
	m4_mesh_build_aabbtree(mesh);
}

static void RenderNonLinearDeformer(SFNode *n, void *rs)
{
	DrawableStack *st = (DrawableStack *) Node_GetPrivate(n);
	M_NonLinearDeformer *nld = (M_NonLinearDeformer*)n;
	RenderEffect3D *eff = (RenderEffect3D *)rs;

	if (!nld->geometry) return;

	/*call render for get_bounds to make sure geometry is up to date*/
	if (eff->traversing_mode==TRAVERSE_GET_BOUNDS) {
		Node_Render(nld->geometry, eff);
	}
	/*invalidated*/
	if (Node_GetDirty(n)) {
		DrawableStack *geo_st = Node_GetPrivate(nld->geometry);
		if (!geo_st) return;
		Node_ClearDirty(n);
		mesh_clone(st->mesh, geo_st->mesh);
		/*apply deforms*/
		NLD_Apply(nld, st->mesh);
	}
	if (eff->traversing_mode==TRAVERSE_GET_BOUNDS) {
		eff->bbox = st->mesh->bounds;
	}
	/*in case there's a cascading of NLDs*/
	else if (!eff->traversing_mode && eff->appear) {
		VS_DrawMesh(eff, st->mesh);
	}
}

void R3D_InitNonLinearDeformer(Render3D *sr, SFNode *node)
{
	BaseDrawableStack(sr->compositor, node);
	Node_SetRenderFunction(node, RenderNonLinearDeformer);
}
