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


#if !defined(GPAC_DISABLE_VRML) && !defined(GPAC_DISABLE_COMPOSITOR) && !defined(GPAC_DISABLE_3D)

#include <gpac/mediaobject.h>

void drawable_3d_base_traverse(GF_Node *n, void *rs, Bool is_destroy, void (*build_shape)(GF_Node*,Drawable3D *,GF_TraverseState *) )
{
	GF_TraverseState *tr_state = (GF_TraverseState *)rs;
	Drawable3D *stack = (Drawable3D*)gf_node_get_private(n);

	if (is_destroy) {
		drawable_3d_del(n);
		return;
	}
	if (gf_node_dirty_get(n)) {
		mesh_reset(stack->mesh);
		GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Compositor] Rebuilding mesh %s\n", gf_node_get_class_name(n)));
		build_shape(n, stack, tr_state);
		gf_node_dirty_clear(n, 0);
	}
	switch (tr_state->traversing_mode) {
	case TRAVERSE_DRAW_3D:
		visual_3d_draw(tr_state, stack->mesh);
		drawable3d_check_focus_highlight(n, tr_state, &stack->mesh->bounds);
		break;
	case TRAVERSE_GET_BOUNDS:
		tr_state->bbox = stack->mesh->bounds;
		break;
	case TRAVERSE_PICK:
		visual_3d_vrml_drawable_pick(n, tr_state, stack->mesh, NULL);
		return;
	case TRAVERSE_SORT:
		//we are drawing 3D object but configured for 2D, force 3D
		if (!tr_state->visual->type_3d && tr_state->visual->compositor->hybrid_opengl) {
			tr_state->visual->compositor->root_visual_setup=0;
			tr_state->visual->compositor->force_type_3d=1;
		}
	}
}

static void build_shape_box(GF_Node *n, Drawable3D *stack, GF_TraverseState *tr_state)
{
	mesh_new_box(stack->mesh, ((M_Box *)n)->size);
}

static void TraverseBox(GF_Node *n, void *rs, Bool is_destroy)
{
	drawable_3d_base_traverse(n, rs, is_destroy, build_shape_box);
}

void compositor_init_box(GF_Compositor *compositor, GF_Node *node)
{
	drawable_3d_new(node);
	gf_node_set_callback_function(node, TraverseBox);
}

static void build_shape_cone(GF_Node *n, Drawable3D *stack, GF_TraverseState *tr_state)
{
	M_Cone *co = (M_Cone *)n;
	mesh_new_cone(stack->mesh, co->height, co->bottomRadius, co->bottom, co->side, tr_state->visual->compositor->fast);
}

static void TraverseCone(GF_Node *n, void *rs, Bool is_destroy)
{
	drawable_3d_base_traverse(n, rs, is_destroy, build_shape_cone);
}

void compositor_init_cone(GF_Compositor *compositor, GF_Node *node)
{
	drawable_3d_new(node);
	gf_node_set_callback_function(node, TraverseCone);
}

static void build_shape_cylinder(GF_Node *n, Drawable3D *stack, GF_TraverseState *tr_state)
{
	M_Cylinder *cy = (M_Cylinder *)n;
	mesh_new_cylinder(stack->mesh, cy->height, cy->radius, cy->bottom, cy->side, cy->top, tr_state->visual->compositor->fast);
}

static void TraverseCylinder(GF_Node *n, void *rs, Bool is_destroy)
{
	drawable_3d_base_traverse(n, rs, is_destroy, build_shape_cylinder);
}

void compositor_init_cylinder(GF_Compositor *compositor, GF_Node *node)
{
	drawable_3d_new(node);
	gf_node_set_callback_function(node, TraverseCylinder);
}

static void build_shape_sphere(GF_Node *n, Drawable3D *stack, GF_TraverseState *tr_state)
{	
	M_Sphere *sp = (M_Sphere *)n;
	mesh_new_sphere(stack->mesh, sp->radius, tr_state->visual->compositor->fast, NULL);
}

void get_tx_coords_from_angle(GF_TraverseState *tr_state, GF_TextureHandler *txh, Bool horizontal, u32 *min_coord, u32 *max_coord)
{
	GF_Vec target, ref;
	Fixed dot, det, hfov, theta_angle, angle_start, angle_end, min_tx, max_tx;
	u32 dim;
	
	ref.x = horizontal ? FIX_ONE : 0;
	ref.y = horizontal ? 0 : FIX_ONE;
	ref.z = 0;

	target = camera_get_target_dir(tr_state->camera);
	if (horizontal) target.y = 0;
	else target.x = 0;
	
	gf_vec_norm(&target);
	dot = gf_vec_dot(target, ref);
	if (horizontal) {
		det = target.x*ref.z - target.z*ref.x;
	} else {
		det = target.y*ref.z - target.z*ref.y;
	}
	theta_angle = gf_atan2(det, dot);
	//sphere starts horizontally at -PI/2
	if (horizontal) {
		theta_angle -= GF_PI2;
		hfov = tr_state->camera->fieldOfView*tr_state->camera->width/tr_state->camera->height/2;
	} else {
		hfov = tr_state->camera->fieldOfView/2;
	}

	angle_start = theta_angle - hfov;
	angle_end = theta_angle + hfov;

	//move everything in [-PI,PI]
	if (angle_start < -GF_PI) angle_start += GF_2PI;
	if (angle_start > GF_PI) angle_start -= GF_2PI;
	if (angle_end < -GF_PI) angle_end += GF_2PI;
	if (angle_end > GF_PI) angle_end -= GF_2PI;
	
	if (horizontal) {
		//start angle corresponds to max tx horiz coord, left to min
		max_tx = FIX_ONE - (angle_start + GF_PI) / GF_2PI;
		min_tx = FIX_ONE - (angle_end + GF_PI) / GF_2PI;
		//we wrap horizontally, which means we may have min_tx > max_tx, in which case we need both [min_tx, WIDTH] and [0, max_tx] parts of the texture
		dim = txh->width;
	} else {
		//-angle is the same position as +angle
		if (angle_start<0) angle_start = -angle_start;
		if (angle_end<0) angle_end = -angle_end;
		//SRD x=0 is the top of the sphere - we don't wrap vertically
		if (angle_start<angle_end) {
			min_tx = angle_start / GF_PI;
			max_tx = angle_end / GF_PI;
		} else {
			max_tx = angle_start / GF_PI;
			min_tx = angle_end / GF_PI;
		}
		dim = txh->height;
	}
	*min_coord= (u32) (min_tx*dim);
	*max_coord= (u32) (max_tx*dim);
}

static void TraverseSphere(GF_Node *n, void *rs, Bool is_destroy)
{
	GF_TraverseState *tr_state = (GF_TraverseState *)rs;
	drawable_3d_base_traverse(n, rs, is_destroy, build_shape_sphere);

	if (!is_destroy && tr_state->traversing_mode == TRAVERSE_DRAW_3D) {
		GF_MediaObjectVRInfo vrinfo;
		u32 min_x, max_x, min_y, max_y;
		GF_TextureHandler *txh = gf_sc_texture_get_handler( ((M_Appearance *) tr_state->appear)->texture );
		
		if (!txh || !txh->stream) return;
		
		if (!gf_mo_get_srd_info(txh->stream, &vrinfo) || !vrinfo.is_tiled_srd)
			return;
		
		//we need to compute min/max tex coords visible for that sphere
		
		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);
		
		gf_mo_hint_visible_rect(txh->stream, min_x, max_x, min_y, max_y);
		GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Compositor] Visible texture rectangle of sphere is %u,%u,%u,%u\n", min_x, max_x, min_y, max_y));
	}
}

void compositor_init_sphere(GF_Compositor *compositor, GF_Node *node)
{
	drawable_3d_new(node);
	gf_node_set_callback_function(node, TraverseSphere);
}

static void build_shape_point_set(GF_Node *n, Drawable3D *stack, GF_TraverseState *tr_state)
{
	M_PointSet *ps = (M_PointSet *)n;
	mesh_new_ps(stack->mesh, ps->coord, ps->color);
}
static void TraversePointSet(GF_Node *n, void *rs, Bool is_destroy)
{
	drawable_3d_base_traverse(n, rs, is_destroy, build_shape_point_set);
}

void compositor_init_point_set(GF_Compositor *compositor, GF_Node *node)
{
	drawable_3d_new(node);
	gf_node_set_callback_function(node, TraversePointSet);
}

static void build_shape_ifs(GF_Node *n, Drawable3D *stack, GF_TraverseState *tr_state)
{
	mesh_new_ifs(stack->mesh, n);
}
static void TraverseIFS(GF_Node *n, void *rs, Bool is_destroy)
{
	drawable_3d_base_traverse(n, rs, is_destroy, build_shape_ifs);
}

static void IFS_SetColorIndex(GF_Node *node, GF_Route *route)
{
	M_IndexedFaceSet *ifs = (M_IndexedFaceSet *)node;
	if (node) {
		gf_sg_vrml_field_copy(&ifs->colorIndex, &ifs->set_colorIndex, GF_SG_VRML_MFINT32);
		gf_sg_vrml_mf_reset(&ifs->set_colorIndex, GF_SG_VRML_MFINT32);
	}
}

static void IFS_SetCoordIndex(GF_Node *node, GF_Route *route)
{
	M_IndexedFaceSet *ifs = (M_IndexedFaceSet *)node;
	if (node) {
		gf_sg_vrml_field_copy(&ifs->coordIndex, &ifs->set_coordIndex, GF_SG_VRML_MFINT32);
		gf_sg_vrml_mf_reset(&ifs->set_coordIndex, GF_SG_VRML_MFINT32);
	}
}

static void IFS_SetNormalIndex(GF_Node *node, GF_Route *route)
{
	M_IndexedFaceSet *ifs = (M_IndexedFaceSet *)node;
	if (node) {
		gf_sg_vrml_field_copy(&ifs->normalIndex, &ifs->set_normalIndex, GF_SG_VRML_MFINT32);
		gf_sg_vrml_mf_reset(&ifs->set_normalIndex, GF_SG_VRML_MFINT32);
	}
}

static void IFS_SetTexCoordIndex(GF_Node *node, GF_Route *route)
{
	M_IndexedFaceSet *ifs = (M_IndexedFaceSet *)node;
	if (node) {
		gf_sg_vrml_field_copy(&ifs->texCoordIndex, &ifs->set_texCoordIndex, GF_SG_VRML_MFINT32);
		gf_sg_vrml_mf_reset(&ifs->set_texCoordIndex, GF_SG_VRML_MFINT32);
	}
}

void compositor_init_ifs(GF_Compositor *compositor, GF_Node *node)
{
	M_IndexedFaceSet *ifs = (M_IndexedFaceSet *)node;
	drawable_3d_new(node);
	gf_node_set_callback_function(node, TraverseIFS);
	ifs->on_set_colorIndex = IFS_SetColorIndex;
	ifs->on_set_coordIndex = IFS_SetCoordIndex;
	ifs->on_set_normalIndex = IFS_SetNormalIndex;
	ifs->on_set_texCoordIndex = IFS_SetTexCoordIndex;

#ifdef GPAC_ENABLE_COVERAGE
	if (gf_sys_is_cov_mode()) {
		IFS_SetCoordIndex(NULL, NULL);
		IFS_SetColorIndex(NULL, NULL);
		IFS_SetNormalIndex(NULL, NULL);
		IFS_SetTexCoordIndex(NULL, NULL);
	}
#endif

}

static void build_shape_ils(GF_Node *n, Drawable3D *stack, GF_TraverseState *tr_state)
{
	M_IndexedLineSet *ils = (M_IndexedLineSet *)n;
	mesh_new_ils(stack->mesh, ils->coord, &ils->coordIndex, ils->color, &ils->colorIndex, ils->colorPerVertex, 0);
}

static void TraverseILS(GF_Node *n, void *rs, Bool is_destroy)
{
	drawable_3d_base_traverse(n, rs, is_destroy, build_shape_ils);
}

static void ILS_SetColorIndex(GF_Node *node, GF_Route *route)
{
	M_IndexedLineSet *ils = (M_IndexedLineSet *)node;
	if (node) {
		gf_sg_vrml_field_copy(&ils->colorIndex, &ils->set_colorIndex, GF_SG_VRML_MFINT32);
		gf_sg_vrml_mf_reset(&ils->set_colorIndex, GF_SG_VRML_MFINT32);
	}
}

static void ILS_SetCoordIndex(GF_Node *node, GF_Route *route)
{
	M_IndexedLineSet *ils = (M_IndexedLineSet *)node;
	if (node) {
		gf_sg_vrml_field_copy(&ils->coordIndex, &ils->set_coordIndex, GF_SG_VRML_MFINT32);
		gf_sg_vrml_mf_reset(&ils->set_coordIndex, GF_SG_VRML_MFINT32);
	}
}

void compositor_init_ils(GF_Compositor *compositor, GF_Node *node)
{
	M_IndexedLineSet *ils = (M_IndexedLineSet *)node;
	drawable_3d_new(node);
	gf_node_set_callback_function(node, TraverseILS);
	ils->on_set_colorIndex = ILS_SetColorIndex;
	ils->on_set_coordIndex = ILS_SetCoordIndex;

#ifdef GPAC_ENABLE_COVERAGE
	if (gf_sys_is_cov_mode()) {
		ILS_SetCoordIndex(NULL, NULL);
		ILS_SetColorIndex(NULL, NULL);
	}
#endif

}


static void build_shape_elevation_grid(GF_Node *n, Drawable3D *stack, GF_TraverseState *tr_state)
{
	mesh_new_elevation_grid(stack->mesh, n);
}

static void TraverseElevationGrid(GF_Node *n, void *rs, Bool is_destroy)
{
	drawable_3d_base_traverse(n, rs, is_destroy, build_shape_elevation_grid);
}

static void ElevationGrid_SetHeight(GF_Node *node, GF_Route *route)
{
	M_ElevationGrid *eg = (M_ElevationGrid *)node;
	if (node) {
		gf_sg_vrml_field_copy(&eg->height, &eg->set_height, GF_SG_VRML_MFFLOAT);
		gf_sg_vrml_mf_reset(&eg->set_height, GF_SG_VRML_MFFLOAT);
	}
}

void compositor_init_elevation_grid(GF_Compositor *compositor, GF_Node *node)
{
	M_ElevationGrid *eg = (M_ElevationGrid *)node;
	drawable_3d_new(node);
	gf_node_set_callback_function(node, TraverseElevationGrid);
	eg->on_set_height = ElevationGrid_SetHeight;
#ifdef GPAC_ENABLE_COVERAGE
	if (gf_sys_is_cov_mode()) {
		ElevationGrid_SetHeight(NULL, NULL);
	}
#endif
}

static void build_shape_extrusion(GF_Node *n, Drawable3D *stack, GF_TraverseState *tr_state)
{
	mesh_new_extrusion(stack->mesh, n);
}

static void TraverseExtrusion(GF_Node *n, void *rs, Bool is_destroy)
{
	drawable_3d_base_traverse(n, rs, is_destroy, build_shape_extrusion);
}

static void Extrusion_SetCrossSection(GF_Node *node, GF_Route *route)
{
	M_Extrusion *eg = (M_Extrusion *)node;
	if (node) {
		gf_sg_vrml_field_copy(&eg->crossSection, &eg->set_crossSection, GF_SG_VRML_MFVEC2F);
		gf_sg_vrml_mf_reset(&eg->set_crossSection, GF_SG_VRML_MFVEC2F);
	}
}
static void Extrusion_SetOrientation(GF_Node *node, GF_Route *route)
{
	M_Extrusion *eg = (M_Extrusion *)node;
	if (node) {
		gf_sg_vrml_field_copy(&eg->orientation, &eg->set_orientation, GF_SG_VRML_MFROTATION);
		gf_sg_vrml_mf_reset(&eg->set_orientation, GF_SG_VRML_MFROTATION);
	}
}
static void Extrusion_SetScale(GF_Node *node, GF_Route *route)
{
	M_Extrusion *eg = (M_Extrusion *)node;
	if (node) {
		gf_sg_vrml_field_copy(&eg->scale, &eg->set_scale, GF_SG_VRML_MFVEC2F);
		gf_sg_vrml_mf_reset(&eg->set_scale, GF_SG_VRML_MFVEC2F);
	}
}
static void Extrusion_SetSpine(GF_Node *node, GF_Route *route)
{
	M_Extrusion *eg = (M_Extrusion *)node;
	if (node) {
		gf_sg_vrml_field_copy(&eg->spine, &eg->set_spine, GF_SG_VRML_MFVEC3F);
		gf_sg_vrml_mf_reset(&eg->set_spine, GF_SG_VRML_MFVEC3F);
	}
}
void compositor_init_extrusion(GF_Compositor *compositor, GF_Node *node)
{
	M_Extrusion *ext = (M_Extrusion *)node;
	drawable_3d_new(node);
	gf_node_set_callback_function(node, TraverseExtrusion);
	ext->on_set_crossSection = Extrusion_SetCrossSection;
	ext->on_set_orientation = Extrusion_SetOrientation;
	ext->on_set_scale = Extrusion_SetScale;
	ext->on_set_spine = Extrusion_SetSpine;

#ifdef GPAC_ENABLE_COVERAGE
	if (gf_sys_is_cov_mode()) {
		Extrusion_SetCrossSection(NULL, NULL);
		Extrusion_SetOrientation(NULL, NULL);
		Extrusion_SetScale(NULL, NULL);
		Extrusion_SetSpine(NULL, NULL);
	}
#endif

}


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


 TODO - think of a way to implement this through the vertex shader
 */

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

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

	l1 = gf_vec_len(v1);
	l2 = gf_vec_len(v2);
	dot = gf_divfix(gf_vec_dot(v1, v2), gf_mulfix(l1, l2));

	r.x = gf_mulfix(v1.y, v2.z) - gf_mulfix(v2.y, v1.z);
	r.y = gf_mulfix(v1.z, v2.x) - gf_mulfix(v2.z, v1.x);
	r.z = gf_mulfix(v1.x, v2.y) - gf_mulfix(v2.x, v1.y);
	r.q = gf_atan2(gf_sqrt(FIX_ONE - gf_mulfix(dot, dot)), dot);
	gf_mx_init(*mx);
	gf_mx_add_rotation(mx, r.q, r.x, r.y, r.z);
	return 1;
}

static GFINLINE 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 = GF_PI;
	}
}

static void NLD_Apply(M_NonLinearDeformer *nld, GF_Mesh *mesh)
{
	u32 i;
	GF_Matrix mx;
	SFVec3f n;
	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++) {
			gf_mx_apply_vec(&mx, &mesh->vertices[i].pos);
			MESH_GET_NORMAL(n, mesh->vertices[i]);
			gf_mx_rotate_vector(&mx, &n);
			MESH_SET_NORMAL(mesh->vertices[i], n);

			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 = gf_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 = gf_divfix(frac-f_min, f_max - f_min);
			val = gf_mulfix(v_max - v_min, frac) + v_min;
		}
		val = gf_mulfix(val, param);

		switch (nld->type) {
		/*taper*/
		case 0:
			mesh->vertices[i].pos.x = gf_mulfix(mesh->vertices[i].pos.x, val);
			mesh->vertices[i].pos.y = gf_mulfix(mesh->vertices[i].pos.y, val);
			MESH_GET_NORMAL(old, mesh->vertices[i]);
			n=old;
			n.x = gf_mulfix(n.x, val);
			n.y = gf_mulfix(n.y, val);
			gf_vec_norm(&n);
			MESH_SET_NORMAL(mesh->vertices[i], n);
			break;
		/*twist*/
		case 1:
			a_cos = gf_cos(val);
			a_sin = gf_sin(val);
			mesh->vertices[i].pos.x = gf_mulfix(a_cos, old.x) - gf_mulfix(a_sin, old.y);
			mesh->vertices[i].pos.y = gf_mulfix(a_sin, old.x) + gf_mulfix(a_cos, old.y);
			MESH_GET_NORMAL(old, mesh->vertices[i]);
			n=old;
			n.x = gf_mulfix(a_cos, old.x) -  gf_mulfix(a_sin, old.y);
			n.y = gf_mulfix(a_sin, old.x) + gf_mulfix(a_cos, old.y);
			gf_vec_norm(&n);
			MESH_SET_NORMAL(mesh->vertices[i], n);
			break;
		/*bend*/
		case 2:
			a_cos = gf_cos(val);
			a_sin = gf_sin(val);
			mesh->vertices[i].pos.x = gf_mulfix(a_sin, old.z) + gf_mulfix(a_cos, old.x);
			mesh->vertices[i].pos.z = gf_mulfix(a_cos, old.z) - gf_mulfix(a_sin, old.x);
			MESH_GET_NORMAL(old, mesh->vertices[i]);
			n=old;
			n.x = gf_mulfix(a_sin, old.z) +  gf_mulfix(a_cos, old.x);
			n.z = gf_mulfix(a_cos, old.z) - gf_mulfix(a_sin, old.x);
			gf_vec_norm(&n);
			MESH_SET_NORMAL(mesh->vertices[i], n);
			break;
		/*pinch, not standard  (taper on X dim only)*/
		case 3:
			mesh->vertices[i].pos.x = gf_mulfix(mesh->vertices[i].pos.x, val);
			MESH_GET_NORMAL(old, mesh->vertices[i]);
			n=old;
			n.x = gf_mulfix(n.x, val);
			gf_vec_norm(&n);
			MESH_SET_NORMAL(mesh->vertices[i], n);
			break;
		}
	}
	if (needs_transform) {
		gf_mx_inverse(&mx);
		for (i=0; i<mesh->v_count; i++) {
			gf_mx_apply_vec(&mx, &mesh->vertices[i].pos);
			MESH_GET_NORMAL(n, mesh->vertices[i]);
			gf_mx_rotate_vector(&mx, &n);
			MESH_SET_NORMAL(mesh->vertices[i], n);
		}
	}
	mesh_update_bounds(mesh);
	gf_mesh_build_aabbtree(mesh);
}

static void build_shape_nld(GF_Node *n, Drawable3D *stack, GF_TraverseState *tr_state)
{
	M_NonLinearDeformer *nld = (M_NonLinearDeformer*)n;
	Drawable3D *geo_st = (Drawable3D *)gf_node_get_private(nld->geometry);

	if (!nld->geometry) return;
	if (!geo_st) return;

	mesh_clone(stack->mesh, geo_st->mesh);
	/*apply deforms*/
	NLD_Apply(nld, stack->mesh);
}

static void TraverseNonLinearDeformer(GF_Node *n, void *rs, Bool is_destroy)
{
	GF_TraverseState *tr_state = (GF_TraverseState *)rs;

	/*traverse geometry for get_bounds to make sure geometry is up to date*/
	if (!is_destroy && (tr_state->traversing_mode==TRAVERSE_GET_BOUNDS)) {
		gf_node_traverse(((M_NonLinearDeformer*)n)->geometry, tr_state);
	}

	drawable_3d_base_traverse(n, rs, is_destroy, build_shape_nld);
}

void compositor_init_non_linear_deformer(GF_Compositor *compositor, GF_Node *node)
{
	drawable_3d_new(node);
	gf_node_set_callback_function(node, TraverseNonLinearDeformer);
}

#endif // !defined(GPAC_DISABLE_VRML) && !defined(GPAC_DISABLE_COMPOSITOR) && !defined(GPAC_DISABLE_3D)
