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


/*PathExtrusion hardcoded proto*/

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

static Bool PathExtrusion_GetNode(SFNode *node, PathExtrusion *path_ext)
{
	FieldInfo field;
	memset(path_ext, 0, sizeof(PathExtrusion));
	if (Node_GetField(node, 0, &field) != M4OK) return 0;
	if (field.fieldType != FT_SFNode) return 0;
	path_ext->geometry = * (SFNode **) field.far_ptr;
	if (Node_GetField(node, 1, &field) != M4OK) return 0;
	if (field.fieldType != FT_MFVec3f) return 0;
	path_ext->spine = (MFVec3f *) field.far_ptr;
	if (Node_GetField(node, 2, &field) != M4OK) return 0;
	if (field.fieldType != FT_SFBool) return 0;
	path_ext->beginCap = *(SFBool *) field.far_ptr;
	if (Node_GetField(node, 3, &field) != M4OK) return 0;
	if (field.fieldType != FT_SFBool) return 0;
	path_ext->endCap = *(SFBool *) field.far_ptr;
	if (Node_GetField(node, 4, &field) != M4OK) return 0;
	if (field.fieldType != FT_SFFloat) return 0;
	path_ext->creaseAngle = *(SFFloat *) field.far_ptr;
	if (Node_GetField(node, 5, &field) != M4OK) return 0;
	if (field.fieldType != FT_MFRotation) return 0;
	path_ext->orientation = (MFRotation *) field.far_ptr;
	if (Node_GetField(node, 6, &field) != M4OK) return 0;
	if (field.fieldType != FT_MFVec2f) return 0;
	path_ext->scale = (MFVec2f *) field.far_ptr;
	if (Node_GetField(node, 7, &field) != M4OK) return 0;
	if (field.fieldType != FT_SFBool) return 0;
	path_ext->txAlongSpine = *(SFBool *) field.far_ptr;
	return 1;
}

static void RenderPathExtrusion(SFNode *node, void *rs)
{
	PathExtrusion path_ext;
	stack2D *st2D;

	RenderEffect3D *eff = (RenderEffect3D *)rs;
	DrawableStack *st = Node_GetPrivate(node);

	if (!PathExtrusion_GetNode(node, &path_ext)) return;
	if (!path_ext.geometry) return;


	if (Node_GetDirty(node)) {
		Node_Render(path_ext.geometry, eff);
		Node_ClearDirty(node);
		switch (Node_GetTag(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:
			st2D = Node_GetPrivate(path_ext.geometry);
			if (!st2D) return;
			mesh_extrude_path(st->mesh, st2D->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:
			Text_Extrude(path_ext.geometry, eff, st->mesh, path_ext.spine, path_ext.creaseAngle, path_ext.beginCap, path_ext.endCap, path_ext.orientation, path_ext.scale, path_ext.txAlongSpine);
			break;
		}
	}

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

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


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

static Bool PlanarExtrusion_GetNode(SFNode *node, PlanarExtrusion *path_ext)
{
	FieldInfo field;
	memset(path_ext, 0, sizeof(PathExtrusion));
	if (Node_GetField(node, 0, &field) != M4OK) return 0;
	if (field.fieldType != FT_SFNode) return 0;
	path_ext->geometry = * (SFNode **) field.far_ptr;
	if (Node_GetField(node, 1, &field) != M4OK) return 0;
	if (field.fieldType != FT_SFNode) return 0;
	path_ext->spine = * (SFNode **) field.far_ptr;
	if (Node_GetField(node, 2, &field) != M4OK) return 0;
	if (field.fieldType != FT_SFBool) return 0;
	path_ext->beginCap = *(SFBool *) field.far_ptr;
	if (Node_GetField(node, 3, &field) != M4OK) return 0;
	if (field.fieldType != FT_SFBool) return 0;
	path_ext->endCap = *(SFBool *) field.far_ptr;
	if (Node_GetField(node, 4, &field) != M4OK) return 0;
	if (field.fieldType != FT_SFFloat) return 0;
	path_ext->creaseAngle = *(SFFloat *) field.far_ptr;
	if (Node_GetField(node, 5, &field) != M4OK) return 0;
	if (field.fieldType != FT_MFFloat) return 0;
	path_ext->orientationKeys = (MFFloat *) field.far_ptr;
	if (Node_GetField(node, 6, &field) != M4OK) return 0;
	if (field.fieldType != FT_MFRotation) return 0;
	path_ext->orientation = (MFRotation *) field.far_ptr;
	if (Node_GetField(node, 7, &field) != M4OK) return 0;
	if (field.fieldType != FT_MFFloat) return 0;
	path_ext->scaleKeys = (MFFloat *) field.far_ptr;
	if (Node_GetField(node, 8, &field) != M4OK) return 0;
	if (field.fieldType != FT_MFVec2f) return 0;
	path_ext->scale = (MFVec2f *) field.far_ptr;
	if (Node_GetField(node, 9, &field) != M4OK) return 0;
	if (field.fieldType != FT_SFBool) return 0;
	path_ext->txAlongSpine = *(SFBool *) field.far_ptr;
	return 1;
}

static void RenderPlanarExtrusion(SFNode *node, void *rs)
{
	PlanarExtrusion plane_ext;
	stack2D *st2D;
	u32 i, j, k;
	MFVec3f spine_vec;
	SFVec3f d;
	Fixed spine_len;
	M4Rect bounds;
	M4Path *geo, *spine;

	RenderEffect3D *eff = (RenderEffect3D *)rs;
	DrawableStack *st = Node_GetPrivate(node);

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


	if (Node_GetDirty(node)) {
		u32 cur, nb_pts;
		geo = spine = NULL;
		Node_Render(plane_ext.geometry, eff);
		Node_Render(plane_ext.spine, eff);
		Node_ClearDirty(node);
		switch (Node_GetTag(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:
			st2D = Node_GetPrivate(plane_ext.geometry);
			if (st2D) geo = st2D->path;
			break;
		default:
			return;
		}
		switch (Node_GetTag(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:
			st2D = Node_GetPrivate(plane_ext.spine);
			if (st2D) spine = st2D->path;
			break;
		default:
			return;
		}
		if (!geo || !spine) return;

		mesh_reset(st->mesh);
		spine = m4_path_flatten(spine);
		m4_path_get_bounds(spine, &bounds);
		geo = m4_path_flatten(geo);
		m4_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;
			VRML_MF_Alloc(&spine_vec, FT_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) {
					m4_vec_diff(d, spine_vec.vals[j], spine_vec.vals[j-1]);
					spine_len += m4_vec_len(d);
				}
			}
			cur += nb_pts;
			if (!plane_ext.orientation->count && !plane_ext.scale->count) {
				mesh_extrude_path_ext(st->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;
				VRML_MF_Alloc(&ori, FT_MFRotation, nb_pts);
				scale.vals = NULL;
				VRML_MF_Alloc(&scale, FT_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) {
						m4_vec_diff(d, spine_vec.vals[j], spine_vec.vals[j-1]);
						cur_len += m4_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 = m4_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 = m4_divfix(frac - plane_ext.orientationKeys->vals[k-1], kDiff);
								break;
							}
							ori.vals[j] = Rotation_Interpolate(plane_ext.orientation->vals[k-1], plane_ext.orientation->vals[k], frac);
						}
					}

					if (plane_ext.scale->count == plane_ext.scaleKeys->count) {
						frac = m4_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 = m4_divfix(frac - plane_ext.scaleKeys->vals[k-1], kDiff);
								break;
							}
							scale.vals[j].x = m4_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 = m4_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(st->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);

				VRML_MF_Reset(&ori, FT_MFRotation);
				VRML_MF_Reset(&scale, FT_MFVec2f);
			}

			VRML_MF_Reset(&spine_vec, FT_MFVec3f);
		}
		mesh_update_bounds(st->mesh);
		m4_path_delete(spine);
		m4_path_delete(geo);
	}

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

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

/*PlaneClipper hardcoded proto*/
typedef struct
{
    SFVec3f normal;
    Fixed dist;
	Chain *children;
} PlaneClipper;

static Bool PlaneClipper_GetNode(SFNode *node, PlaneClipper *pc)
{
	FieldInfo field;
	memset(pc, 0, sizeof(PlaneClipper));
	if (Node_GetField(node, 0, &field) != M4OK) return 0;
	if (field.fieldType != FT_SFVec3f) return 0;
	pc->normal = * (SFVec3f *) field.far_ptr;
	if (Node_GetField(node, 1, &field) != M4OK) return 0;
	if (field.fieldType != FT_SFFloat) return 0;
	pc->dist = * (SFFloat *) field.far_ptr;
	if (Node_GetField(node, 2, &field) != M4OK) return 0;
	if (field.fieldType != FT_MFNode) return 0;
	pc->children = *(Chain **) field.far_ptr;
	return 1;
}

static void RenderPlaneClipper(SFNode *node, void *rs)
{
	PlaneClipper pc;
	M4Plane p;
	GroupingNode *st = Node_GetPrivate(node);
	RenderEffect3D *eff = (RenderEffect3D *) rs;
	if (!PlaneClipper_GetNode(node, &pc)) return;

	if (eff->num_clip_planes==MAX_USER_CLIP_PLANES) {
		grouping_traverse(st, eff, NULL);
		return;
	}

	p.normal = pc.normal;
	p.d = pc.dist;
	eff->clip_planes[eff->num_clip_planes] = p;
	m4_mx_apply_plane(&eff->model_matrix, &eff->clip_planes[eff->num_clip_planes]);
	eff->num_clip_planes++;

	if (eff->traversing_mode == TRAVERSE_SORT) {
		M4Plane p;
		p.normal = pc.normal;
		p.d = pc.dist;
		VS3D_SetClipPlane(eff->surface, p);
		grouping_traverse(st, eff, NULL);
		VS3D_ResetClipPlane(eff->surface);
	} else {
		grouping_traverse(st, eff, NULL);
	}

	eff->num_clip_planes--;
}

void R3D_InitPlaneClipper(Render3D *sr, SFNode *node)
{
	PlaneClipper pc;
	if (PlaneClipper_GetNode(node, &pc)) {
		GroupingNode *stack = malloc(sizeof(GroupingNode));
		SetupGroupingNode(stack, sr->compositor, node, pc.children);
		Node_SetPrivate(node, stack);
		Node_SetPreDestroyFunction(node, DestroyBaseGrouping);
		Node_SetRenderFunction(node, RenderPlaneClipper);
		/*we're a grouping node, force bounds rebuild as soon as loaded*/
		Node_SetDirtyFlag(node, SG_CHILD_DIRTY);
	}
}


/*hardcoded proto loading - this is mainly used for module development and testing...*/
void R3D_InitHardcodedProto(Render3D *sr, SFNode *node)
{
	MFURL *proto_url;
	LPPROTO proto;
	u32 i;

	proto = Node_GetProto(node);
	if (!proto) return;
	proto_url = Proto_GetExternURLFieldPointer(proto);

	for (i=0; i<proto_url->count; i++) {
		const char *url = proto_url->vals[0].url;
		if (!strnicmp(url, "urn:inet:gpac:builtin:PathExtrusion", 22 + 13)) {
			R3D_InitPathExtrusion(sr, node);
			return;
		}
		if (!strnicmp(url, "urn:inet:gpac:builtin:PlanarExtrusion", 22 + 15)) {
			R3D_InitPlanarExtrusion(sr, node);
			return;
		}
		if (!strnicmp(url, "urn:inet:gpac:builtin:PlaneClipper", 22 + 12)) {
			R3D_InitPlaneClipper(sr, node);
			return;
		}
		if (!strnicmp(url, "urn:inet:gpac:builtin:TextureText", 22 + 11)) {
			void R3D_InitTextureText(Render3D *sr, SFNode *node);
			R3D_InitTextureText(sr, node);
			return;
		}
	}
}

