root/src/compositor/visual_manager_3d.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. drawable_3d_new
  2. drawable_3d_del
  3. drawable3d_check_focus_highlight
  4. visual_3d_setup_traversing_state
  5. visual_3d_viewpoint_change
  6. visual_3d_setup_projection
  7. visual_3d_draw_background
  8. visual_3d_draw_background_on_axis
  9. visual_3d_init_draw
  10. visual_3d_has_alpha
  11. visual_3d_register_context
  12. visual_3d_flush_contexts
  13. visual_3d_draw_node
  14. visual_3d_setup_clipper
  15. visual_3d_draw_frame
  16. reset_collide_cursor
  17. visual_3d_check_collisions
  18. visual_3d_node_cull
  19. visual_3d_pick_node
  20. visual_3d_vrml_drawable_pick
  21. visual_3d_vrml_drawable_collide
  22. visual_3d_setup_texture_2d
  23. visual_3d_set_2d_strike
  24. visual_3d_draw_2d_with_aspect
  25. visual_3d_draw_2d
  26. visual_3d_draw_from_context
  27. visual_3d_setup_material
  28. visual_3d_setup_texture
  29. visual_3d_disable_texture
  30. visual_3d_setup_appearance
  31. visual_3d_draw
  32. visual_3d_projection_matrix_modified
  33. visual_3d_enable_headlight
  34. visual_3d_set_material_2d
  35. visual_3d_set_material_2d_argb
  36. visual_3d_set_clipper_2d
  37. visual_3d_reset_clipper_2d
  38. visual_3d_set_clip_plane
  39. visual_3d_reset_clip_plane
  40. visual_3d_set_material
  41. visual_3d_set_shininess
  42. visual_3d_set_state
  43. visual_3d_set_texture_matrix
  44. visual_3d_has_inactive_light
  45. visual_3d_add_point_light
  46. visual_3d_add_spot_light
  47. visual_3d_add_directional_light
  48. visual_3d_remove_last_light
  49. visual_3d_clear_all_lights
  50. visual_3d_set_fog

/*
 *                      GPAC - Multimedia Framework C SDK
 *
 *                      Authors: Jean Le Feuvre
 *                      Copyright (c) Telecom ParisTech 2000-2012
 *                                      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 "texturing.h"
#include "nodes_stacks.h"
#include <gpac/options.h>

#ifndef GPAC_DISABLE_3D



/*generic drawable 3D constructor/destructor*/
Drawable3D *drawable_3d_new(GF_Node *node)
{
        Drawable3D *tmp;
        GF_SAFEALLOC(tmp, Drawable3D);
        if (!tmp) {
                GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to allocate drawable 3D stack\n"));
                return NULL;
        }
        tmp->mesh = new_mesh();
        gf_node_set_private(node, tmp);
        return tmp;
}
void drawable_3d_del(GF_Node *n)
{
        Drawable3D *d = (Drawable3D *)gf_node_get_private(n);
        if (d) {
                if (d->mesh) mesh_free(d->mesh);
                gf_free(d);
        }
        gf_sc_check_focus_upon_destroy(n);
}


void drawable3d_check_focus_highlight(GF_Node *node, GF_TraverseState *tr_state, GF_BBox *orig_bounds)
{
        Drawable *hlight;
        GF_Node *prev_node;
        u32 prev_mode;
        GF_BBox *bounds;
        GF_Matrix cur;
        GF_Compositor *compositor = tr_state->visual->compositor;

        if (compositor->disable_focus_highlight) return;

        if (compositor->focus_node!=node) return;

        hlight = compositor->focus_highlight;
        if (!hlight) return;

        /*check if focus node has changed*/
        prev_node = gf_node_get_private(hlight->node);
        if (prev_node != node) {
                gf_node_set_private(hlight->node, node);

                drawable_reset_path(hlight);
                gf_path_reset(hlight->path);
        }
        /*this is a grouping node, get its bounds*/
        if (!orig_bounds) {
                gf_mx_copy(cur, tr_state->model_matrix);
                gf_mx_init(tr_state->model_matrix);
                prev_mode = tr_state->traversing_mode;
                tr_state->traversing_mode = TRAVERSE_GET_BOUNDS;
                tr_state->bbox.is_set = 0;

//              gf_sc_get_nodes_bounds(node, ((GF_ParentNode *)node)->children, tr_state, 1);
                gf_node_traverse_children(node, tr_state);

                tr_state->traversing_mode = prev_mode;
                gf_mx_copy(tr_state->model_matrix, cur);
                bounds = &tr_state->bbox;
        } else {
                bounds = orig_bounds;
        }
        visual_3d_draw_bbox(tr_state, bounds);
}


static void visual_3d_setup_traversing_state(GF_VisualManager *visual, GF_TraverseState *tr_state)
{
        tr_state->visual = visual;
        tr_state->camera = &visual->camera;
#ifndef GPAC_DISABLE_VRML
        tr_state->backgrounds = visual->back_stack;
        tr_state->viewpoints = visual->view_stack;
        tr_state->fogs = visual->fog_stack;
        tr_state->navigations = visual->navigation_stack;
#endif
        tr_state->color_mat.identity = 1;
        tr_state->camera->vp.x = tr_state->camera->vp.y = 0;
        tr_state->min_hsize = INT2FIX(MIN(visual->width, visual->height) / 2);
        if (!tr_state->min_hsize) tr_state->min_hsize = FIX_ONE;


        /*main visual, set AR*/
        if (visual->compositor->visual==visual) {
                if (tr_state->visual->compositor->has_size_info) {
                        tr_state->camera->vp.x = INT2FIX(tr_state->visual->compositor->vp_x);
                        tr_state->camera->vp.y = INT2FIX(tr_state->visual->compositor->vp_y);
                        tr_state->camera->vp.width = INT2FIX(tr_state->visual->compositor->vp_width);
                        tr_state->camera->vp.height = INT2FIX(tr_state->visual->compositor->vp_height);

                        /*2D ortho, scale is already present in the root user transform*/
                        if (visual->type_3d==0) {
                                tr_state->camera->width = INT2FIX(tr_state->visual->width);
                                tr_state->camera->height = INT2FIX(tr_state->visual->height);
                        } else {
                                tr_state->camera->width = INT2FIX(tr_state->visual->compositor->vp_width);
                                tr_state->camera->height = INT2FIX(tr_state->visual->compositor->vp_height);
                        }
                } else {
                        Fixed sw, sh;
                        sw = INT2FIX(tr_state->visual->compositor->vp_width);
                        sh = INT2FIX(tr_state->visual->compositor->vp_height);
                        /*AR changed, rebuild camera*/

                        if (tr_state->visual->compositor->recompute_ar
                                || (sw!=tr_state->camera->vp.width)
                                || (sh!=tr_state->camera->vp.height)) {
                                tr_state->camera->width = tr_state->camera->vp.width = INT2FIX(tr_state->visual->compositor->vp_width);
                                tr_state->camera->height = tr_state->camera->vp.height = INT2FIX(tr_state->visual->compositor->vp_height);
                                tr_state->camera->flags |= CAM_IS_DIRTY;
                        }
                }
        }
        /*composite visual, no AR*/
        else {
                tr_state->camera->vp.width = tr_state->camera->width = INT2FIX(visual->width);
                tr_state->camera->vp.height = tr_state->camera->height = INT2FIX(visual->height);
        }

        if (!tr_state->pixel_metrics) {
                if (tr_state->camera->height > tr_state->camera->width) {
                        tr_state->camera->height = 2*gf_divfix(tr_state->camera->height , tr_state->camera->width);
                        tr_state->camera->width = 2*FIX_ONE;
                } else {
                        tr_state->camera->width = 2 * gf_divfix(tr_state->camera->width, tr_state->camera->height);
                        tr_state->camera->height = 2 * FIX_ONE;
                }
        }
        /*setup bounds*/
        tr_state->bbox.max_edge.x = tr_state->camera->width / 2;
        tr_state->bbox.min_edge.x = -tr_state->bbox.max_edge.x;
        tr_state->bbox.max_edge.y = tr_state->camera->height / 2;
        tr_state->bbox.min_edge.y = -tr_state->bbox.max_edge.y;
        tr_state->bbox.max_edge.z = tr_state->bbox.min_edge.z = 0;
        tr_state->bbox.is_set = 1;
}


void visual_3d_viewpoint_change(GF_TraverseState *tr_state, GF_Node *vp, Bool animate_change, Fixed fieldOfView, SFVec3f position, SFRotation orientation, SFVec3f local_center)
{
        Fixed dist;
        SFVec3f d;


        /*update znear&zfar*/
        tr_state->camera->z_near = tr_state->camera->avatar_size.x ;

        if (tr_state->camera->z_near<=0) tr_state->camera->z_near = FIX_ONE/2;
        /*if pixel metrics, the default znear may be way too far and lead to weird navigation*/
        else if (tr_state->camera->z_near>=FIX_ONE) tr_state->camera->z_near = FIX_ONE/2;
        tr_state->camera->z_near /= 2;
        tr_state->camera->z_far = tr_state->camera->visibility;

        /*z_far is selected so that an object the size of the viewport measures
        one pixel when located at the far plane. It can be found through the projection
        transformation (projection matrix) of x and y
                x transformation is: x'= (1/(ar*tg(fov/2)) )*x/z
                y transformation is: y'=(1/(tg(fov/2)))*x/z

        therefore when z=z_far and x=max(width/2, height/2), then
        x' = 1/max(vp_size.x, vp_size.y) (transformed OpenGL viewport measures one)

        this yields z_far = max(vp_size.x, vp_size.y) * max(width/2, height/2) * max(1/(ar*tg(fov/2)), 1/tg(fov/2))
                z_far = max(vp_size.x, vp_size.y) * max(width, height) / (2*min(1, ar)*tg(fov/2)) )

        to choose a z_far so that the size is more than one pixel, then z_far' = z_far/n_pixels*/
        if (tr_state->camera->z_far<=0) {
                Fixed ar = gf_divfix(tr_state->vp_size.x, tr_state->vp_size.y);
                if (ar>FIX_ONE) ar = FIX_ONE;
                tr_state->camera->z_far = gf_muldiv(
                                              MAX(tr_state->vp_size.x,tr_state->vp_size.y),
                                              MAX(tr_state->camera->width, tr_state->camera->height),
                                              gf_mulfix(ar*2, gf_tan(fieldOfView/2))
                                          );

                /*fixed-point overflow*/
                if (tr_state->camera->z_far <= tr_state->camera->z_near) {
                        tr_state->camera->z_far = FIX_MAX/4;
                }
        }
        if (vp) {
                /*now check if vp is in pixel metrics. If not then:
                - either it's in the main scene, there's nothing to do
                - or it's in an inline, and the inline has been scaled if main scene is in pm: nothing to do*/
                if (0 && gf_sg_use_pixel_metrics(gf_node_get_graph(vp))) {
                        GF_Matrix mx;
                        gf_mx_init(mx);
                        gf_mx_add_scale(&mx, tr_state->min_hsize, tr_state->min_hsize, tr_state->min_hsize);
                        gf_mx_apply_vec(&mx, &position);
                        gf_mx_apply_vec(&mx, &local_center);
                }
        }
        /*default VP setup - this is undocumented in the spec. Default VP pos is (0, 0, 10) but not really nice
        in pixel metrics. We set z so that we see just the whole visual*/
        else if (tr_state->pixel_metrics) {
                if (tr_state->visual != tr_state->visual->compositor->visual) {
                        position.z = gf_divfix(tr_state->camera->width, 2*gf_tan(fieldOfView/2) );
                } else {
                        position.z = gf_mulfix(position.z, tr_state->min_hsize);
                }
        }
#ifdef GF_SR_USE_DEPTH
        /* 3D world calibration for stereoscopic screen */
        if (tr_state->visual->compositor->auto_calibration && tr_state->visual->compositor->video_out->view_distance) {
                Fixed view_distance, disparity;

                /*get view distance in pixels*/
                view_distance = tr_state->visual->compositor->video_out->view_distance * tr_state->visual->compositor->video_out->dpi_x;
                view_distance = gf_divfix(view_distance , FLT2FIX(2.54f) );
                disparity = INT2FIX(tr_state->visual->compositor->video_out->disparity);

                if (tr_state->visual->depth_vp_range) {
                        position.z = view_distance;
                        tr_state->camera->z_near = view_distance - tr_state->visual->depth_vp_position + tr_state->visual->depth_vp_range/2;
                        tr_state->camera->z_far = view_distance - tr_state->visual->depth_vp_position - tr_state->visual->depth_vp_range/2;
                }
                else if (disparity) {
                        /*3,4 cm = 1,3386 inch -> pixels*/
                        Fixed half_interocular_dist_pixel = FLT2FIX(1.3386) * tr_state->visual->compositor->video_out->dpi_x;

                        //frustum placed to match user's real viewpoint
                        position.z = view_distance;

                        //near plane will match front side of the display's stereoscopic box
                        //-> n=D- (dD)/(e+d)
                        tr_state->camera->z_near = view_distance -
                                                   gf_divfix( gf_mulfix(disparity,view_distance), (half_interocular_dist_pixel + disparity));
                }
                else if (tr_state->visual->compositor->display_depth) {
                        Fixed dist = INT2FIX(tr_state->visual->compositor->display_depth);
                        if (dist<0) dist = INT2FIX(tr_state->visual->height);

#if 1
                        view_distance = gf_divfix(tr_state->visual->height, 2*gf_tan(fieldOfView/2) );
#else
                        view_distance = gf_muldiv(view_distance, tr_state->visual->height, tr_state->visual->compositor->video_out->max_screen_height);
                        fieldOfView = 2*gf_atan2(tr_state->visual->height/2, view_distance);
#endif

                        //frustum placed to match user's real viewpoint
                        position.z = view_distance;
                        tr_state->camera->z_near = view_distance - 2*dist/3;
                        tr_state->camera->z_far = view_distance + dist/2;
                }
        }
#endif

        gf_vec_diff(d, position, local_center);
        dist = gf_vec_len(d);

        if (!dist || (dist<tr_state->camera->z_near) || (dist > tr_state->camera->z_far)) {
                if (dist > tr_state->camera->z_far)
                        tr_state->camera->z_far = 2*dist;

                dist = 10 * tr_state->camera->avatar_size.x;
                if ((dist<tr_state->camera->z_near) || (dist > tr_state->camera->z_far))
                        dist = (tr_state->camera->avatar_size.x + tr_state->camera->z_far) / 5;
        }
        tr_state->camera->vp_dist = dist;
        tr_state->camera->vp_position = position;
        tr_state->camera->vp_orientation = orientation;
        tr_state->camera->vp_fov = fieldOfView;
        tr_state->camera->examine_center = local_center;

        camera_reset_viewpoint(tr_state->camera, animate_change);
        if (tr_state->layer3d) gf_node_dirty_set(tr_state->layer3d, GF_SG_VRML_BINDABLE_DIRTY, 0);
        gf_sc_invalidate(tr_state->visual->compositor, NULL);
}

void visual_3d_setup_projection(GF_TraverseState *tr_state, Bool is_layer)
{
#ifndef GPAC_DISABLE_VRML
        GF_Node *bindable;
#endif
        u32 mode = tr_state->traversing_mode;
        tr_state->traversing_mode = TRAVERSE_BINDABLE;

        /*setup viewpoint (this directly modifies the frustum)*/
#ifndef GPAC_DISABLE_VRML
        bindable = (GF_Node*)gf_list_get(tr_state->viewpoints, 0);
        if (Bindable_GetIsBound(bindable)) {
                gf_node_traverse(bindable, tr_state);
                tr_state->camera->had_viewpoint = 1;
        } else
#endif
                if (tr_state->camera->had_viewpoint) {
                        u32 had_vp = tr_state->camera->had_viewpoint;
                        tr_state->camera->had_viewpoint = 0;
                        if (tr_state->camera->is_3D) {
                                SFVec3f pos, center;
                                SFRotation r;
                                Fixed fov = GF_PI/4;
#ifdef GF_SR_USE_DEPTH
                                /* 3D world calibration for stereoscopic screen */
                                if (tr_state->visual->compositor->auto_calibration && tr_state->visual->compositor->video_out->view_distance) {
                                        /*get view distance in pixels*/
                                        Fixed view_distance = tr_state->visual->compositor->video_out->view_distance * tr_state->visual->compositor->video_out->dpi_x;
                                        view_distance = gf_divfix(view_distance , FLT2FIX(2.54f) );

                                        fov = 2*gf_atan2( INT2FIX(tr_state->visual->compositor->video_out->max_screen_width)/2, view_distance);
                                }
#endif

                                /*default viewpoint*/
                                pos.x = pos.y = 0;
                                pos.z = INT2FIX(10);
                                center.x = center.y = center.z = 0;
                                r.q = r.x = r.z = 0;
                                r.y = FIX_ONE;
                                /*this takes care of pixelMetrics*/
                                visual_3d_viewpoint_change(tr_state, NULL, 0, fov, pos, r, center);
                                /*initial vp compute, don't animate*/
                                if (had_vp == 2) {
                                        camera_stop_anim(tr_state->camera);
                                        camera_reset_viewpoint(tr_state->camera, 0);
                                        /*scene not yet ready, force a recompute of world bounds at next frame*/
                                        if (!is_layer && gf_sc_fit_world_to_screen(tr_state->visual->compositor) == 0) {
                                                tr_state->camera->had_viewpoint = 2;
                                        }
                                }
                        } else {
                                tr_state->camera->flags &= ~CAM_HAS_VIEWPORT;
                                tr_state->camera->flags |= CAM_IS_DIRTY;
                        }
                }

        if (tr_state->visual->nb_views>1) {
                s32 view_idx;
                Fixed interocular_dist_pixel;
                Fixed delta = 0;

                interocular_dist_pixel = tr_state->visual->compositor->interoccular_distance + tr_state->visual->compositor->interoccular_offset;

                view_idx = tr_state->visual->current_view;
                view_idx -= tr_state->visual->nb_views/2;
                delta = interocular_dist_pixel * view_idx;
                if (! (tr_state->visual->nb_views % 2)) {
                        delta += interocular_dist_pixel/2;
                }
                if (tr_state->visual->reverse_views) delta = - delta;

                tr_state->camera->flags |= CAM_IS_DIRTY;
                camera_update_stereo(tr_state->camera, &tr_state->transform, tr_state->visual->center_coords, delta, tr_state->visual->compositor->video_out->view_distance, tr_state->visual->compositor->focus_distance, tr_state->visual->camera_layout);
        } else {
                camera_update(tr_state->camera, &tr_state->transform, tr_state->visual->center_coords);
        }

        /*setup projection (we do this to avoidloading the projection matrix for each draw operation in non-GL3/GLES2 modes
                modelview will be loaded at each object
        */
        visual_3d_projection_matrix_modified(tr_state->visual);
        gf_mx_init(tr_state->model_matrix);

        tr_state->traversing_mode = mode;
#ifdef GF_SR_USE_DEPTH
        tr_state->depth_offset = 0;
        tr_state->depth_gain = FIX_ONE;
#endif
}

static void visual_3d_draw_background(GF_TraverseState *tr_state, u32 layer_type)
{
        u32 mode;
#ifndef GPAC_DISABLE_VRML
        GF_Node *bindable;
#endif

        /*setup background*/
        mode = tr_state->traversing_mode;
        tr_state->traversing_mode = TRAVERSE_BINDABLE;

        /*if in layer clear z buffer (even if background)*/
        if (layer_type) visual_3d_clear_depth(tr_state->visual);

        /*clear requested - do it before background drawing for layer3D (transparent background)*/
        if (layer_type==2) {
                SFColor col;
                col.red = INT2FIX((tr_state->visual->compositor->back_color>>16)&0xFF) / 255;
                col.green = INT2FIX((tr_state->visual->compositor->back_color>>8)&0xFF) / 255;
                col.blue = INT2FIX((tr_state->visual->compositor->back_color)&0xFF) / 255;
                visual_3d_clear(tr_state->visual, col, 0);
        }

#ifndef GPAC_DISABLE_VRML
        bindable = (GF_Node*) gf_list_get(tr_state->backgrounds, 0);
        if (Bindable_GetIsBound(bindable)) {
                gf_node_traverse(bindable, tr_state);
        }
        /*clear if not in layer*/
        else
#endif
                if (!layer_type) {
                        SFColor col;
                        Fixed alpha = 0;
                        col.red = INT2FIX((tr_state->visual->compositor->back_color>>16)&0xFF) / 255;
                        col.green = INT2FIX((tr_state->visual->compositor->back_color>>8)&0xFF) / 255;
                        col.blue = INT2FIX((tr_state->visual->compositor->back_color)&0xFF) / 255;
                        /*if composite visual, clear with alpha = 0*/
                        if (tr_state->visual==tr_state->visual->compositor->visual) {
                                alpha = FIX_ONE;
                                if (tr_state->visual->compositor->user && (tr_state->visual->compositor->user->init_flags & GF_TERM_WINDOW_TRANSPARENT) ) {
                                        alpha = 0;
                                }
                        }
                        visual_3d_clear(tr_state->visual, col, alpha);
                }
        tr_state->traversing_mode = mode;

}

/*in off-axis, projection matrix is not aligned with view axis, however we need to draw the background
centered !!
In this case we draw the background before setting up the projection but after setting up scissor and Viewport*/
static void visual_3d_draw_background_on_axis(GF_TraverseState *tr_state, u32 layer_type)
{
        GF_Matrix proj, model;
        GF_Camera *cam = &tr_state->visual->camera;
        Fixed ar = gf_divfix(cam->width, cam->height);

        tr_state->visual->camera_layout = 0;
        gf_mx_copy(proj, cam->projection);
        gf_mx_copy(model, cam->modelview);

        gf_mx_perspective(&cam->projection, cam->fieldOfView, ar, cam->z_near, cam->z_far);
        visual_3d_projection_matrix_modified(tr_state->visual);

        /*setup modelview*/
        gf_mx_lookat(&cam->modelview, cam->position, cam->target, cam->up);

        visual_3d_draw_background(tr_state, layer_type);

        tr_state->visual->camera_layout = GF_3D_CAMERA_OFFAXIS;
        gf_mx_copy(cam->projection, proj);
        gf_mx_copy(cam->modelview, model);


}

void visual_3d_init_draw(GF_TraverseState *tr_state, u32 layer_type)
{
#ifndef GPAC_DISABLE_VRML
        GF_Node *bindable;
#endif
        Bool off_axis_background = (tr_state->camera->is_3D && (tr_state->visual->camera_layout==GF_3D_CAMERA_OFFAXIS)) ? 1 : 0;

        /*if not in layer, traverse navigation node
        FIXME: we should update the nav info according to the world transform at the current viewpoint (vrml)*/
        tr_state->traversing_mode = TRAVERSE_BINDABLE;
#ifndef GPAC_DISABLE_VRML
        bindable = tr_state->navigations ? (GF_Node*) gf_list_get(tr_state->navigations, 0) : NULL;
        if (Bindable_GetIsBound(bindable)) {
                gf_node_traverse(bindable, tr_state);
                tr_state->camera->had_nav_info = 1;
        } else
#endif
                if (tr_state->camera->had_nav_info) {
                        /*if no navigation specified, use default VRML one*/
                        tr_state->camera->avatar_size.x = FLT2FIX(0.25f);
                        tr_state->camera->avatar_size.y = FLT2FIX(1.6f);
                        tr_state->camera->avatar_size.z = FLT2FIX(0.75f);
                        tr_state->camera->visibility = 0;
                        tr_state->camera->speed = FIX_ONE;
                        /*not specified in the spec, but by default we forbid navigation in layer*/
                        if (layer_type) {
                                tr_state->camera->navigation_flags = NAV_HEADLIGHT;
                                tr_state->camera->navigate_mode = GF_NAVIGATE_NONE;
                        } else {
                                tr_state->camera->navigation_flags = NAV_ANY | NAV_HEADLIGHT;
                                if (tr_state->camera->is_3D) {
                                        if (tr_state->visual->compositor->default_navigation_mode != GF_NAVIGATE_NONE) {
                                                tr_state->camera->navigate_mode = tr_state->visual->compositor->default_navigation_mode;
                                        } else {
                                                /*X3D is by default examine, VRML/MPEG4 is WALK*/
                                                tr_state->camera->navigate_mode = (tr_state->visual->type_3d==3) ? GF_NAVIGATE_EXAMINE : GF_NAVIGATE_WALK;
                                        }

#ifdef GF_SR_USE_DEPTH
                                        /*                              if (tr_state->visual->compositor->display_depth)
                                                                                tr_state->camera->navigate_mode = GF_NAVIGATE_NONE;
                                        */
#endif
                                } else {
                                        tr_state->camera->navigate_mode = GF_NAVIGATE_NONE;
                                }
                        }
                        tr_state->camera->had_nav_info = 0;

                        if (tr_state->pixel_metrics) {
                                tr_state->camera->visibility = gf_mulfix(tr_state->camera->visibility, tr_state->min_hsize);
                                tr_state->camera->avatar_size.x = gf_mulfix(tr_state->camera->avatar_size.x, tr_state->min_hsize);
                                tr_state->camera->avatar_size.y = gf_mulfix(tr_state->camera->avatar_size.y, tr_state->min_hsize);
                                tr_state->camera->avatar_size.z = gf_mulfix(tr_state->camera->avatar_size.z, tr_state->min_hsize);
                        }
                }

        /*animate current camera - if returns TRUE draw next frame*/
        if (camera_animate(tr_state->camera)) {
                if (tr_state->visual->compositor->active_layer) gf_node_dirty_set(tr_state->visual->compositor->active_layer, 0, 1);
                gf_sc_invalidate(tr_state->visual->compositor, NULL);
        }


        /*turn off depth buffer in 2D*/
        visual_3d_enable_depth_buffer(tr_state->visual, tr_state->camera->is_3D);

        if ((tr_state->visual->autostereo_type==GF_3D_STEREO_SIDE) || (tr_state->visual->autostereo_type==GF_3D_STEREO_HEADSET)) {
                GF_Rect orig_vp = tr_state->camera->vp;
                Fixed vp_width = orig_vp.width;
//              Fixed vp_height = orig_vp.height;
                Fixed old_w = tr_state->camera->width;
                Fixed old_h = tr_state->camera->height;

                //fill up the entire screen matchin AR
                if (tr_state->visual->autostereo_type==GF_3D_STEREO_HEADSET) {
                        Fixed max_width = INT2FIX(tr_state->visual->compositor->display_width) / tr_state->visual->nb_views;
                        Fixed max_height = INT2FIX(tr_state->visual->compositor->display_height);

#if 0
                                Fixed ratio = gf_divfix(vp_width, vp_height);

                                if (max_width < gf_mulfix(ratio, max_height) ) {
                                        tr_state->camera->vp.width = max_width;
                                } else {
                                        tr_state->camera->vp.width = gf_mulfix(ratio, max_height);
                                }
                                tr_state->camera->vp.height = gf_divfix(tr_state->camera->vp.width, ratio);
#else
                                //fill max of screen
                                tr_state->camera->vp.width = max_width;
                                tr_state->camera->vp.height = max_height;
#endif
                                tr_state->camera->width = max_width;
                                tr_state->camera->height = max_height;

                        tr_state->camera->vp.x = (INT2FIX(tr_state->visual->compositor->display_width) - tr_state->visual->nb_views*tr_state->camera->vp.width)/2 + tr_state->visual->current_view * tr_state->camera->vp.width;
                        tr_state->camera->vp.y = (INT2FIX(tr_state->visual->compositor->display_height) - tr_state->camera->vp.height)/2;

                } else {
                        tr_state->camera->vp.width = vp_width / tr_state->visual->nb_views;
                        tr_state->camera->vp.x += tr_state->visual->current_view * tr_state->camera->vp.width;
                }
                //if first view clear up the original vp
                if (!tr_state->visual->current_view) {
                        SFColor col;
                        col.red = INT2FIX((tr_state->visual->compositor->back_color>>16)&0xFF) / 255;
                        col.green = INT2FIX((tr_state->visual->compositor->back_color>>8)&0xFF) / 255;
                        col.blue = INT2FIX((tr_state->visual->compositor->back_color)&0xFF) / 255;
                        visual_3d_set_viewport(tr_state->visual, orig_vp);
                        visual_3d_clear(tr_state->visual, col, 0);
                }

                visual_3d_set_viewport(tr_state->visual, tr_state->camera->vp);
                //we must set scissor in side-by-side to avoid drawing the background outside the viewport!
                visual_3d_set_scissor(tr_state->visual, &tr_state->camera->vp);

                if (off_axis_background) {
                        visual_3d_draw_background_on_axis(tr_state, layer_type);
                }

                /*setup projection*/
                visual_3d_setup_projection(tr_state, layer_type);

                tr_state->camera->vp = orig_vp;
                tr_state->camera->width = old_w;
                tr_state->camera->height = old_h;

        } else if (tr_state->visual->autostereo_type==GF_3D_STEREO_TOP) {
                GF_Rect orig_vp;
                orig_vp = tr_state->camera->vp;

                tr_state->camera->vp.height = tr_state->camera->vp.height / tr_state->visual->nb_views;

                if (tr_state->visual == tr_state->visual->compositor->visual) {
                        Fixed remain = INT2FIX(tr_state->visual->compositor->output_height - orig_vp.height) / tr_state->visual->nb_views;

                        tr_state->camera->vp.y = remain/2 + tr_state->visual->current_view*remain + tr_state->visual->current_view *tr_state->camera->vp.height;
                } else {
                        tr_state->camera->vp.y = tr_state->visual->height - tr_state->camera->vp.y - tr_state->camera->vp.height;
                }
                visual_3d_set_viewport(tr_state->visual, tr_state->camera->vp);
                //we must set scissor in top-to-bottom to avoid drawing the background outside the viewport!
                visual_3d_set_scissor(tr_state->visual, &tr_state->camera->vp);

                if (off_axis_background) {
                        visual_3d_draw_background_on_axis(tr_state, layer_type);
                }

                /*setup projection*/
                visual_3d_setup_projection(tr_state, layer_type);

                tr_state->camera->vp = orig_vp;
        } else {
                visual_3d_set_viewport(tr_state->visual, tr_state->camera->vp);

                if (off_axis_background) {
                        visual_3d_draw_background_on_axis(tr_state, layer_type);
                }

                /*setup projection*/
                visual_3d_setup_projection(tr_state, layer_type);

                if (tr_state->visual->autostereo_type) {
                        visual_3d_clear_depth(tr_state->visual);
                }
        }

        /*regular background draw*/
        if (!tr_state->camera->is_3D || tr_state->visual->camera_layout != GF_3D_CAMERA_OFFAXIS) {
                visual_3d_draw_background(tr_state, layer_type);
        }

        /*set headlight if any*/
        if (tr_state->visual->type_3d>1) {
                visual_3d_enable_headlight(tr_state->visual, (tr_state->camera->navigation_flags & NAV_HEADLIGHT) ? 1 : 0, tr_state->camera);
        }

        //reset scissor
        visual_3d_set_scissor(tr_state->visual, NULL);
}


static GFINLINE Bool visual_3d_has_alpha(GF_TraverseState *tr_state, GF_Node *geom)
{
        Bool is_mat3D;
        Drawable3D *stack;


        is_mat3D = 0;
#ifndef GPAC_DISABLE_VRML
        if (tr_state->appear) {
                GF_Node *mat = ((M_Appearance *)tr_state->appear)->material;
                if (mat) {
                        u32 tag = gf_node_get_tag(mat);
                        switch (tag) {
                        /*for M2D: if filled & transparent we're transparent - otherwise we must check texture*/
                        case TAG_MPEG4_Material2D:
                                if (((M_Material2D *)mat)->filled && ((M_Material2D *)mat)->transparency) return 1;
                                break;
                        case TAG_MPEG4_Material:
#ifndef GPAC_DISABLE_X3D
                        case TAG_X3D_Material:
#endif
                                is_mat3D = 1;
                                if ( ((M_Material *)mat)->transparency) return 1;
                                break;
                        case TAG_MPEG4_MaterialKey:
                                return 1;
                                break;
                        }
                } else if (tr_state->camera->is_3D) {
                        GF_TextureHandler *txh = gf_sc_texture_get_handler(((M_Appearance *)tr_state->appear)->texture);
                        if (txh && txh->transparent) return 1;
                }
        }

        /*check alpha texture in3D or with bitmap*/
        if (tr_state->appear && ( is_mat3D || (gf_node_get_tag(geom)==TAG_MPEG4_Bitmap))) {
                GF_TextureHandler *txh = gf_sc_texture_get_handler(((M_Appearance *)tr_state->appear)->texture);
                if (txh && txh->transparent) return 1;
        }

#endif /*GPAC_DISABLE_VRML*/

        /*TODO - FIXME check alpha only...*/
        if (!tr_state->color_mat.identity) return 1;

        stack = (Drawable3D *)gf_node_get_private(geom);
        if (stack && stack->mesh && (stack->mesh->flags & MESH_HAS_ALPHA)) return 1;
        return 0;
}

void visual_3d_register_context(GF_TraverseState *tr_state, GF_Node *geometry)
{
        u32 i, count;
        DirectionalLightContext *nl, *ol;
        Drawable3DContext *ctx;
        Drawable3D *drawable;

        assert(tr_state->traversing_mode == TRAVERSE_SORT);

        drawable = (Drawable3D*)gf_node_get_private(geometry);

        /*if 2D draw in declared order. Otherwise, if no alpha or node is a layer, draw directly
        if mesh is not setup yet, consider it as opaque*/
        if (!tr_state->camera->is_3D || !visual_3d_has_alpha(tr_state, geometry) || !drawable->mesh) {
                tr_state->traversing_mode = TRAVERSE_DRAW_3D;
                /*layout/form clipper, set it in world coords only*/
                if (tr_state->has_clip) {
                        visual_3d_set_clipper_2d(tr_state->visual, tr_state->clipper, NULL);
                }

                gf_node_traverse(geometry, tr_state);

                /*clear appearance flag once drawn in 3D - not doing so would prevent
                dirty flag propagation in the tree, whcih would typically prevent redrawing
                layers/offscreen groups when texture changes*/
                if (tr_state->appear)
                        gf_node_dirty_clear(tr_state->appear, 0);

                /*back to SORT*/
                tr_state->traversing_mode = TRAVERSE_SORT;

                if (tr_state->has_clip) visual_3d_reset_clipper_2d(tr_state->visual);
                return;
        }

        GF_SAFEALLOC(ctx, Drawable3DContext);
        if (!ctx) {
                GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to allocate drawable 3D context\n"));
                return;
        }
        
        ctx->directional_lights = gf_list_new();
        ctx->geometry = geometry;
        ctx->appearance = tr_state->appear;

        memcpy(&ctx->model_matrix, &tr_state->model_matrix, sizeof(GF_Matrix));
        ctx->color_mat.identity = tr_state->color_mat.identity;
        if (!tr_state->color_mat.identity) memcpy(&ctx->color_mat, &tr_state->color_mat, sizeof(GF_ColorMatrix));

        ctx->pixel_metrics = tr_state->pixel_metrics;
        ctx->text_split_idx = tr_state->text_split_idx;

        i=0;
        while ((ol = (DirectionalLightContext*)gf_list_enum(tr_state->local_lights, &i))) {
                nl = (DirectionalLightContext*)gf_malloc(sizeof(DirectionalLightContext));
                memcpy(nl, ol, sizeof(DirectionalLightContext));
                gf_list_add(ctx->directional_lights, nl);
        }
        ctx->clipper = tr_state->clipper;
        ctx->has_clipper = tr_state->has_clip;
        ctx->cull_flag = tr_state->cull_flag;

        if ((ctx->num_clip_planes = tr_state->num_clip_planes))
                memcpy(ctx->clip_planes, tr_state->clip_planes, sizeof(GF_Plane)*MAX_USER_CLIP_PLANES);

        /*get bbox and and insert from further to closest*/
        tr_state->bbox = drawable->mesh->bounds;

        gf_mx_apply_bbox(&ctx->model_matrix, &tr_state->bbox);
        gf_mx_apply_bbox(&tr_state->camera->modelview, &tr_state->bbox);
        ctx->zmax = tr_state->bbox.max_edge.z;
#ifdef GF_SR_USE_DEPTH
        ctx->depth_offset=tr_state->depth_offset;
#endif

        /*we don't need an exact sorting, as long as we keep transparent nodes above  -note that for
        speed purposes we store in reverse-z transparent nodes*/
        count = gf_list_count(tr_state->visual->alpha_nodes_to_draw);
        for (i=0; i<count; i++) {
                Drawable3DContext *next = (Drawable3DContext *)gf_list_get(tr_state->visual->alpha_nodes_to_draw, i);
                if (next->zmax>ctx->zmax) {
                        gf_list_insert(tr_state->visual->alpha_nodes_to_draw, ctx, i);
                        return;
                }
        }
        gf_list_add(tr_state->visual->alpha_nodes_to_draw, ctx);
}

void visual_3d_flush_contexts(GF_VisualManager *visual, GF_TraverseState *tr_state)
{
        u32 i, idx, count;
        Bool pixel_metrics = tr_state->pixel_metrics;

        tr_state->traversing_mode = TRAVERSE_DRAW_3D;

        count = gf_list_count(visual->alpha_nodes_to_draw);
        for (idx=0; idx<count; idx++) {
                DirectionalLightContext *dl;
                Drawable3DContext *ctx = (Drawable3DContext *)gf_list_get(visual->alpha_nodes_to_draw, idx);

                /*apply directional lights*/
                tr_state->local_light_on = 1;
                i=0;
                while ((dl = (DirectionalLightContext*)gf_list_enum(ctx->directional_lights, &i))) {
                        GF_Matrix mx;
                        gf_mx_copy(mx, tr_state->model_matrix);

                        gf_mx_add_matrix(&tr_state->model_matrix, &dl->light_matrix);
                        gf_node_traverse(dl->dlight, tr_state);

                        gf_mx_copy(tr_state->model_matrix, mx);
                }

                /*clipper, set it in world coords only*/
                if (ctx->has_clipper) {
                        visual_3d_set_clipper_2d(visual, ctx->clipper, NULL);
                }

                /*clip planes, set it in world coords only*/
                for (i=0; i<ctx->num_clip_planes; i++)
                        visual_3d_set_clip_plane(visual, ctx->clip_planes[i], NULL, 0);

                /*restore traversing state*/
                memcpy(&tr_state->model_matrix, &ctx->model_matrix, sizeof(GF_Matrix));
                tr_state->color_mat.identity = ctx->color_mat.identity;
                if (!tr_state->color_mat.identity) memcpy(&tr_state->color_mat, &ctx->color_mat, sizeof(GF_ColorMatrix));
                tr_state->text_split_idx = ctx->text_split_idx;
                tr_state->pixel_metrics = ctx->pixel_metrics;
                /*restore cull flag in case we're completely inside (avoids final frustum/AABB tree culling)*/
                tr_state->cull_flag = ctx->cull_flag;

                tr_state->appear = ctx->appearance;
#ifdef GF_SR_USE_DEPTH
                tr_state->depth_offset = ctx->depth_offset;
#endif
                gf_node_traverse(ctx->geometry, tr_state);

                /*clear appearance flag once drawn in 3D - not doing so would prevent
                dirty flag propagation in the tree, whcih would typically prevent redrawing
                layers/offscreen groups when texture changes*/
                if (tr_state->appear) {
                        gf_node_dirty_clear(tr_state->appear, 0);
                        tr_state->appear = NULL;
                }

                /*reset directional lights*/
                tr_state->local_light_on = 0;
                for (i=gf_list_count(ctx->directional_lights); i>0; i--) {
                        DirectionalLightContext *dl = (DirectionalLightContext*)gf_list_get(ctx->directional_lights, i-1);
                        gf_node_traverse(dl->dlight, tr_state);
                        gf_free(dl);
                }

                if (ctx->has_clipper) visual_3d_reset_clipper_2d(visual);
                for (i=0; i<ctx->num_clip_planes; i++) visual_3d_reset_clip_plane(visual);

                /*and destroy*/
                gf_list_del(ctx->directional_lights);
                gf_free(ctx);
        }
        tr_state->pixel_metrics = pixel_metrics;
        gf_list_reset(tr_state->visual->alpha_nodes_to_draw);
}
static void visual_3d_draw_node(GF_TraverseState *tr_state, GF_Node *root_node)
{
#ifndef GPAC_DISABLE_VRML
        GF_Node *fog;
#endif
        if (!tr_state->camera || !tr_state->visual) return;

        visual_3d_init_draw(tr_state, 0);

        /*main visual, handle collisions*/
        if ((tr_state->visual==tr_state->visual->compositor->visual) && tr_state->camera->is_3D)
                visual_3d_check_collisions(tr_state, root_node, NULL);

#ifndef GPAC_DISABLE_VRML
        /*setup fog*/
        fog = (GF_Node*) gf_list_get(tr_state->visual->fog_stack, 0);
        tr_state->traversing_mode = TRAVERSE_BINDABLE;
        if (Bindable_GetIsBound(fog)) gf_node_traverse(fog, tr_state);
#endif

        /*turn global lights on*/
        if (tr_state->visual->type_3d>1) {
                tr_state->traversing_mode = TRAVERSE_LIGHTING;
                gf_node_traverse(root_node, tr_state);
        }

        /*sort graph*/
        tr_state->traversing_mode = TRAVERSE_SORT;
        gf_node_traverse(root_node, tr_state);

        /*and draw*/
        visual_3d_flush_contexts(tr_state->visual, tr_state);

        /*and turn off lights*/
        visual_3d_clear_all_lights(tr_state->visual);
}

void visual_3d_setup_clipper(GF_VisualManager *visual, GF_TraverseState *tr_state)
{
        GF_Rect rc;
        /*setup top clipper*/
        if (visual->center_coords) {
                rc = gf_rect_center(INT2FIX(visual->width), INT2FIX(visual->height));
        } else {
                rc.width = INT2FIX(visual->width);
                rc.height = INT2FIX(visual->height);
                rc.x = 0;
                rc.y = rc.height;
                if (visual->compositor->visual==visual) {
                        rc.x += INT2FIX(visual->compositor->vp_x);
                        rc.y += INT2FIX(visual->compositor->vp_y);
                }
        }

        /*setup viewport*/
#ifndef GPAC_DISABLE_VRML
        if (gf_list_count(visual->view_stack)) {
                tr_state->traversing_mode = TRAVERSE_BINDABLE;
                tr_state->bounds = rc;
                gf_node_traverse((GF_Node *) gf_list_get(visual->view_stack, 0), tr_state);
        }
#endif

        visual->top_clipper = gf_rect_pixelize(&rc);
        tr_state->clipper = rc;
        gf_mx_init(tr_state->layer_matrix);
}

Bool visual_3d_draw_frame(GF_VisualManager *visual, GF_Node *root, GF_TraverseState *tr_state, Bool is_root_visual)
{
#ifndef GPAC_DISABLE_LOG
        u32 time = gf_sys_clock();
#endif

        visual_3d_setup(visual);
        visual->glsl_flags = 0;

        /*setup our traversing state*/
        visual_3d_setup_traversing_state(visual, tr_state);

        if (is_root_visual) {
                Bool auto_stereo = 0;

                visual_3d_setup_clipper(visual, tr_state);

                if (tr_state->visual->autostereo_type > GF_3D_STEREO_LAST_SINGLE_BUFFER) {
                        visual_3d_init_autostereo(visual);
                        auto_stereo = 1;
                }

#ifndef GPAC_USE_GLES1X
                visual_3d_init_shaders(visual);
#endif

                for (visual->current_view=0; visual->current_view < visual->nb_views; visual->current_view++) {
                        GF_SceneGraph *sg;
                        u32 i;
                        visual_3d_draw_node(tr_state, root);

                        /*extra scene graphs*/
                        i=0;
                        while ((sg = (GF_SceneGraph*)gf_list_enum(visual->compositor->extra_scenes, &i))) {
                                tr_state->traversing_mode = TRAVERSE_SORT;
                                gf_sc_traverse_subscene(visual->compositor, root, sg, tr_state);
                        }

                        if (auto_stereo) {
                                visual_3d_end_auto_stereo_pass(visual);
                        }

                        if (is_root_visual) visual->compositor->reset_graphics = 0;
                }

        } else {
                visual_3d_draw_node(tr_state, root);
        }
        GF_LOG(GF_LOG_DEBUG, GF_LOG_RTI, ("[RTI] Frame\t%d\t3D drawn in \t%d\tms\n", visual->compositor->frame_number, gf_sys_clock() - time));

        return 1;
}

static void reset_collide_cursor(GF_Compositor *compositor)
{
        if (compositor->sensor_type == GF_CURSOR_COLLIDE) {
                GF_Event evt;
                compositor->sensor_type = evt.cursor.cursor_type = GF_CURSOR_NORMAL;
                evt.type = GF_EVENT_SET_CURSOR;
                compositor->video_out->ProcessEvent(compositor->video_out, &evt);
        }
}

void visual_3d_check_collisions(GF_TraverseState *tr_state, GF_Node *on_node, GF_ChildNodeItem *node_list)
{
        SFVec3f n, dir;
        Bool go;
        Fixed diff, pos_diff;

        assert(tr_state->visual && tr_state->camera);
        /*don't collide on VP animations or when modes discard collision*/
        if ((tr_state->camera->anim_len && !tr_state->camera->jumping) || !tr_state->visual->compositor->collide_mode || (tr_state->camera->navigate_mode>=GF_NAVIGATE_EXAMINE)) {
                /*reset ground flag*/
                tr_state->camera->last_had_ground = 0;
                /*and avoid reseting move at next collision change*/
                tr_state->camera->last_pos = tr_state->camera->position;
                return;
        }
        /*don't collide if not moved*/
        if (gf_vec_equal(tr_state->camera->position, tr_state->camera->last_pos)) {
                reset_collide_cursor(tr_state->visual->compositor);
                return;
        }
        tr_state->traversing_mode = TRAVERSE_COLLIDE;
        tr_state->camera->collide_flags = 0;
        tr_state->camera->collide_dist = FIX_MAX;
        tr_state->camera->ground_dist = FIX_MAX;
        if ((tr_state->camera->navigate_mode==GF_NAVIGATE_WALK) && tr_state->visual->compositor->gravity_on) tr_state->camera->collide_flags |= CF_DO_GRAVITY;

        gf_vec_diff(dir, tr_state->camera->position, tr_state->camera->last_pos);
        pos_diff = gf_vec_len(dir);
        gf_vec_norm(&dir);

        diff = 0;
        go = 1;
        tr_state->camera->last_had_col = 0;
        /*some explanation: the current collision detection algo only checks closest distance
        to objects, but doesn't attempt to track object cross during a move. If we step more than
        the collision detection size, we may cross an object without detecting collision. we thus
        break the move in max collision size moves*/
        while (go) {
                if (pos_diff>tr_state->camera->avatar_size.x) {
                        pos_diff-=tr_state->camera->avatar_size.x;
                        diff += tr_state->camera->avatar_size.x;
                } else {
                        diff += pos_diff;
                        go = 0;
                }
                n = gf_vec_scale(dir, diff);
                gf_vec_add(tr_state->camera->position, tr_state->camera->last_pos, n);
                if (on_node) {
                        gf_node_traverse(on_node, tr_state);
                } else if (node_list) {
                        while (node_list) {
                                gf_node_traverse(node_list->node, tr_state);
                                node_list = node_list->next;
                        }
                }
                if (tr_state->camera->collide_flags & CF_COLLISION) break;
                tr_state->camera->collide_flags &= ~CF_DO_GRAVITY;
        }

        /*gravity*/
        if (tr_state->camera->collide_flags & CF_GRAVITY) {
                diff = tr_state->camera->ground_dist - tr_state->camera->avatar_size.y;
                if (tr_state->camera->last_had_ground && (-diff>tr_state->camera->avatar_size.z)) {
                        GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Collision] Obstacle detected - too high (dist %g)\n", FIX2FLT(diff)));
                        tr_state->camera->position = tr_state->camera->last_pos;
                        tr_state->camera->flags |= CAM_IS_DIRTY;
                } else {
                        if ((tr_state->camera->jumping && fabs(diff)>tr_state->camera->dheight)
                                || (!tr_state->camera->jumping && (ABS(diff)>FIX_ONE/1000) )) {
                                tr_state->camera->last_had_ground = 1;
                                n = gf_vec_scale(tr_state->camera->up, -diff);
                                GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Collision] Ground detected camera position: %g %g %g - offset: %g %g %g (dist %g)\n",
                                                                      FIX2FLT(tr_state->camera->position.x), FIX2FLT(tr_state->camera->position.y), FIX2FLT(tr_state->camera->position.z),
                                                                      FIX2FLT(n.x), FIX2FLT(n.y), FIX2FLT(n.z), FIX2FLT(diff)));

                                gf_vec_add(tr_state->camera->position, tr_state->camera->position, n);
                                gf_vec_add(tr_state->camera->target, tr_state->camera->target, n);
                                gf_vec_add(tr_state->camera->last_pos, tr_state->camera->position, n);
                                tr_state->camera->flags |= CAM_IS_DIRTY;
                        }
                }
        }
        /*collsion found*/
        if (tr_state->camera->collide_flags & CF_COLLISION) {
                if (tr_state->visual->compositor->sensor_type != GF_CURSOR_COLLIDE) {
                        GF_Event evt;
                        tr_state->camera->last_had_col = 1;
                        evt.type = GF_EVENT_SET_CURSOR;
                        tr_state->visual->compositor->sensor_type = evt.cursor.cursor_type = GF_CURSOR_COLLIDE;
                        tr_state->visual->compositor->video_out->ProcessEvent(tr_state->visual->compositor->video_out, &evt);
                }

                /*regular collision*/
                if (tr_state->visual->compositor->collide_mode==GF_COLLISION_NORMAL) {
                        tr_state->camera->position = tr_state->camera->last_pos;
                        tr_state->camera->flags |= CAM_IS_DIRTY;
                        GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Collision] Collision detected - restoring previous avatar position\n"));
                } else {
                        /*camera displacement collision*/
                        if (tr_state->camera->collide_dist) {
                                if (tr_state->camera->collide_dist>=tr_state->camera->avatar_size.x)
                                        GF_LOG(GF_LOG_WARNING, GF_LOG_COMPOSE, ("[Collision] Collision distance %g greater than avatar collide size %g\n", FIX2FLT(tr_state->camera->collide_dist), FIX2FLT(tr_state->camera->avatar_size.x)));

                                /*safety check due to precision, always stay below collide dist*/
                                if (tr_state->camera->collide_dist>=tr_state->camera->avatar_size.x) tr_state->camera->collide_dist = tr_state->camera->avatar_size.x;

                                gf_vec_diff(n, tr_state->camera->position, tr_state->camera->collide_point);
                                gf_vec_norm(&n);
                                n = gf_vec_scale(n, tr_state->camera->avatar_size.x - tr_state->camera->collide_dist);
                                GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Collision] offseting camera: position: %g %g %g - offset: %g %g %g\n",
                                                                      FIX2FLT(tr_state->camera->position.x), FIX2FLT(tr_state->camera->position.y), FIX2FLT(tr_state->camera->position.z),
                                                                      FIX2FLT(n.x), FIX2FLT(n.y), FIX2FLT(n.z)));

                                gf_vec_add(tr_state->camera->position, tr_state->camera->position, n);
                                gf_vec_add(tr_state->camera->target, tr_state->camera->target, n);
                        } else {
                                GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Collision] Collision detected and camera on hit point - restoring previous avatar position\n"));
                                tr_state->camera->position = tr_state->camera->last_pos;
                        }
                        tr_state->camera->last_pos = tr_state->camera->position;
                        tr_state->camera->flags |= CAM_IS_DIRTY;
                }
        } else {
                reset_collide_cursor(tr_state->visual->compositor);
                tr_state->camera->last_pos = tr_state->camera->position;
                GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Collision] no collision found\n"));
        }

        if (tr_state->camera->flags & CAM_IS_DIRTY) visual_3d_setup_projection(tr_state, 0);
}

/*uncomment to disable frustum cull*/
//#define DISABLE_VIEW_CULL

#ifndef GPAC_DISABLE_LOG
static const char *szPlaneNames [] =
{
        "Near", "Far", "Left", "Right", "Bottom", "Top"
};
#endif

Bool visual_3d_node_cull(GF_TraverseState *tr_state, GF_BBox *bbox, Bool skip_near)
{
#ifdef DISABLE_VIEW_CULL
        tr_state->cull_flag = CULL_INSIDE;
        return 1;
#else
        GF_BBox b;
        Fixed irad, rad;
        GF_Camera *cam;
        Bool do_sphere;
        u32 i, p_idx;
        SFVec3f cdiff, vertices[8];

        if (!tr_state->camera || (tr_state->cull_flag == CULL_INSIDE)) return 1;
        assert(tr_state->cull_flag != CULL_OUTSIDE);

        /*empty bounds*/
        if (!bbox->is_set) {
                tr_state->cull_flag = CULL_OUTSIDE;
                GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Culling] Node out (bbox not set)\n"));
                return 0;
        }

        /*get bbox sphere in world space*/
        b = *bbox;
        gf_mx_apply_bbox_sphere(&tr_state->model_matrix, &b);
        cam = tr_state->camera;

        /*if camera is inside bbox consider we intersect*/
        if (gf_bbox_point_inside(&b, &cam->position)) {
                tr_state->cull_flag = CULL_INTERSECTS;
                GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Culling] Node intersect (camera in box test)\n"));
                return 1;
        }
        /*first check: sphere vs frustum sphere intersection, this will discard far objects quite fast*/
        if (tr_state->camera->is_3D) {
                gf_vec_diff(cdiff, cam->center, b.center);
                rad = b.radius + cam->radius;
                if (gf_vec_len(cdiff) > rad) {
                        tr_state->cull_flag = CULL_OUTSIDE;
                        GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Culling] Node out (sphere-sphere test)\n"));
                        return 0;
                }
        }

        /*second check: sphere vs frustum planes intersection, if any intersection is detected switch
        to n/p vertex check.*/
        rad = b.radius;
        irad = -b.radius;
        do_sphere = 1;

        /*skip near/far tests in ortho mode, and near in 3D*/
        i = (tr_state->camera->is_3D) ? (skip_near ? 1 : 0) : 2;
        for (; i<6; i++) {
                Fixed d;
                if (do_sphere) {
                        d = gf_plane_get_distance(&cam->planes[i], &b.center);
                        if (d<irad) {
                                tr_state->cull_flag = CULL_OUTSIDE;
                                GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Culling] Node out (sphere-planes test) plane %s\n", szPlaneNames[i]));
                                return 0;
                        }
                        /*intersect, move to n-p vertex test*/
                        if (d<rad) {
                                /*get full bbox in world coords*/
                                b = *bbox;
                                gf_mx_apply_bbox(&tr_state->model_matrix, &b);
                                /*get box vertices*/
                                gf_bbox_get_vertices(b.min_edge, b.max_edge, vertices);
                                do_sphere = 0;
                        } else {
                                continue;
                        }
                }
                p_idx = cam->p_idx[i];
                /*check p-vertex: if not in plane, we're out (since p-vertex is the closest point to the plane)*/
                d = gf_plane_get_distance(&cam->planes[i], &vertices[p_idx]);
                if (d<0) {
                        tr_state->cull_flag = CULL_OUTSIDE;
                        GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Culling] Node out (p-vertex test) plane %s - Distance %g\n", szPlaneNames[i], FIX2FLT(d) ));
                        return 0;
                }

                /*check n-vertex: if not in plane, we're intersecting - don't check for near and far planes*/
                if (i>1) {
                        d = gf_plane_get_distance(&cam->planes[i], &vertices[7-p_idx]);
                        if (d<0) {
                                tr_state->cull_flag = CULL_INTERSECTS;
                                GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Culling] Node intersect (n-vertex test) plane %s - Distance %g\n", szPlaneNames[i], FIX2FLT(d) ));
                                return 1;
                        }
                }
        }

        tr_state->cull_flag = CULL_INSIDE;
        GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Culling] Node inside (%s test)\n", do_sphere ? "sphere-planes" : "n-p vertex"));
        return 1;
#endif
}

void visual_3d_pick_node(GF_VisualManager *visual, GF_TraverseState *tr_state, GF_Event *ev, GF_ChildNodeItem *children)
{
        Fixed in_x, in_y, x, y;
        SFVec3f start, end;
        SFVec4f res;

        visual_3d_setup_traversing_state(visual, tr_state);
        visual_3d_setup_projection(tr_state, 0);

        x = INT2FIX(ev->mouse.x);
        y = INT2FIX(ev->mouse.y);

        /*if coordinate system is not centered, move to centered coord before applying camera transform
        because the (un)projection matrices include this transform*/
        if (!visual->center_coords) {
                x = x - INT2FIX(tr_state->camera->width)/2;
                y = INT2FIX(tr_state->camera->height)/2 - y;
        }


        /*main visual with AR*/
        if ((visual->compositor->visual == visual) && visual->compositor->has_size_info) {
                Fixed scale = gf_divfix(INT2FIX(visual->width), INT2FIX(visual->compositor->vp_width));
                x = gf_mulfix(x, scale);
                scale = gf_divfix(INT2FIX(visual->height), INT2FIX(visual->compositor->vp_height));
                y = gf_mulfix(y, scale);
        }

        start.z = visual->camera.z_near;
        end.z = visual->camera.z_far;
        if (!tr_state->camera->is_3D && !tr_state->pixel_metrics) {
                start.x = end.x = gf_divfix(x, tr_state->min_hsize);
                start.y = end.y = gf_divfix(y, tr_state->min_hsize);
        } else {
                start.x = end.x = x;
                start.y = end.y = y;
        }

        /*unproject to world coords*/
        in_x = 2*x/ (s32) visual->width;
        in_y = 2*y/ (s32) visual->height;

        res.x = in_x;
        res.y = in_y;
        res.z = -FIX_ONE;
        res.q = FIX_ONE;
        gf_mx_apply_vec_4x4(&visual->camera.unprojection, &res);
        if (!res.q) return;
        start.x = gf_divfix(res.x, res.q);
        start.y = gf_divfix(res.y, res.q);
        start.z = gf_divfix(res.z, res.q);

        res.x = in_x;
        res.y = in_y;
        res.z = FIX_ONE;
        res.q = FIX_ONE;
        gf_mx_apply_vec_4x4(&visual->camera.unprojection, &res);
        if (!res.q) return;
        end.x = gf_divfix(res.x, res.q);
        end.y = gf_divfix(res.y, res.q);
        end.z = gf_divfix(res.z, res.q);

        tr_state->ray = gf_ray(start, end);
        /*also update hit info world ray in case we have a grabbed sensor with mouse off*/
        visual->compositor->hit_world_ray = tr_state->ray;

        GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Picking] cast ray\n\tOrigin %.4f %.4f %.4f - End %.4f %.4f %.4f\n\tDir %.4f %.4f %.4f\n",
                                              FIX2FLT(tr_state->ray.orig.x), FIX2FLT(tr_state->ray.orig.y), FIX2FLT(tr_state->ray.orig.z),
                                              FIX2FLT(end.x), FIX2FLT(end.y), FIX2FLT(end.z),
                                              FIX2FLT(tr_state->ray.dir.x), FIX2FLT(tr_state->ray.dir.y), FIX2FLT(tr_state->ray.dir.z)));



        visual->compositor->hit_square_dist = 0;
        visual->compositor->hit_node = NULL;
        gf_list_reset(visual->compositor->sensors);

        /*not the root scene, use children list*/
        if (visual->compositor->visual != visual) {
                while (children) {
                        gf_node_traverse(children->node, tr_state);
                        children = children->next;
                }
        } else {
                gf_node_traverse(gf_sg_get_root_node(visual->compositor->scene), tr_state);
        }
}


#ifndef GPAC_DISABLE_VRML
void visual_3d_vrml_drawable_pick(GF_Node *n, GF_TraverseState *tr_state, GF_Mesh *mesh, Drawable *drawable)
{
        SFVec3f local_pt, world_pt, vdiff;
        SFVec3f hit_normal;
        SFVec2f text_coords;
        u32 i, count;
        Fixed sqdist;
        Bool node_is_over;
        GF_Compositor *compositor;
        GF_Matrix mx;
        GF_Ray r;
        u32 cull_bckup = tr_state->cull_flag;

        if (!mesh && !drawable) return;

        count = gf_list_count(tr_state->vrml_sensors);
        compositor = tr_state->visual->compositor;

        if (mesh) {
                if (mesh->mesh_type!=MESH_TRIANGLES)
                        return;
                if (!visual_3d_node_cull(tr_state, &mesh->bounds, 0)) {
                        tr_state->cull_flag = cull_bckup;
                        return;
                }
        }
        tr_state->cull_flag = cull_bckup;
        r = tr_state->ray;
        gf_mx_copy(mx, tr_state->model_matrix);
        gf_mx_inverse(&mx);
        gf_mx_apply_ray(&mx, &r);

        /*if we already have a hit point don't check anything below...*/
        if (compositor->hit_square_dist && !compositor->grabbed_sensor && !tr_state->layer3d) {
                GF_Plane p;
                GF_BBox box;
                SFVec3f hit = compositor->hit_world_point;
                gf_mx_apply_vec(&mx, &hit);
                p.normal = r.dir;
                p.d = -1 * gf_vec_dot(p.normal, hit);
                if (mesh)
                        box = mesh->bounds;
                else
                        gf_bbox_from_rect(&box, &drawable->path->bbox);

                if (gf_bbox_plane_relation(&box, &p) == GF_BBOX_FRONT) {
                        GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Picking] bounding box of node %s (DEF %s) below current hit point - skipping\n", gf_node_get_class_name(n), gf_node_get_name(n)));
                        return;
                }
        }
        if (drawable) {
                DrawAspect2D asp;
                node_is_over = 0;
                if (compositor_get_2d_plane_intersection(&r, &local_pt)) {
                        if (gf_path_point_over(drawable->path, local_pt.x, local_pt.y)) {
                                hit_normal.x = hit_normal.y = 0;
                                hit_normal.z = FIX_ONE;
                                text_coords.x = gf_divfix(local_pt.x, drawable->path->bbox.width) + FIX_ONE/2;
                                text_coords.y = gf_divfix(local_pt.y, drawable->path->bbox.height) + FIX_ONE/2;
                                node_is_over = 1;
                        }
                        memset(&asp, 0, sizeof(DrawAspect2D));
                        drawable_get_aspect_2d_mpeg4(drawable->node, &asp, tr_state);
                        if (asp.pen_props.width || asp.line_texture ) {
                                StrikeInfo2D *si = drawable_get_strikeinfo(tr_state->visual->compositor, drawable, &asp, tr_state->appear, NULL, 0, NULL);
                                if (si && si->outline && gf_path_point_over(si->outline, local_pt.x, local_pt.y)) {
                                        hit_normal.x = hit_normal.y = 0;
                                        hit_normal.z = FIX_ONE;
                                        text_coords.x = gf_divfix(local_pt.x, si->outline->bbox.width) + FIX_ONE/2;
                                        text_coords.y = gf_divfix(local_pt.y, si->outline->bbox.height) + FIX_ONE/2;
                                        node_is_over = 1;
                                }
                        }
                }
        } else {
                node_is_over = gf_mesh_intersect_ray(mesh, &r, &local_pt, &hit_normal, &text_coords);
        }

        if (!node_is_over) return;

        /*check distance from user and keep the closest hitpoint*/
        world_pt = local_pt;
        gf_mx_apply_vec(&tr_state->model_matrix, &world_pt);

        for (i=0; i<tr_state->num_clip_planes; i++) {
                if (gf_plane_get_distance(&tr_state->clip_planes[i], &world_pt) < 0) {
                        GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Picking] node %s (def %s) is not in clipper half space\n", gf_node_get_class_name(n), gf_node_get_name(n)));
                        return;
                }
        }

        gf_vec_diff(vdiff, world_pt, tr_state->ray.orig);
        sqdist = gf_vec_lensq(vdiff);
        if (compositor->hit_square_dist && (compositor->hit_square_dist+FIX_EPSILON<sqdist)) {
                GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Picking] node %s (def %s) is farther (%g) than current pick (%g)\n", gf_node_get_class_name(n), gf_node_get_name(n), FIX2FLT(sqdist), FIX2FLT(compositor->hit_square_dist)));
                return;
        }

        compositor->hit_square_dist = sqdist;
        gf_list_reset(compositor->sensors);
        for (i=0; i<count; i++) {
                gf_list_add(compositor->sensors, gf_list_get(tr_state->vrml_sensors, i));
        }

        gf_mx_copy(compositor->hit_world_to_local, tr_state->model_matrix);
        gf_mx_copy(compositor->hit_local_to_world, mx);
        compositor->hit_local_point = local_pt;
        compositor->hit_world_point = world_pt;
        compositor->hit_world_ray = tr_state->ray;
        compositor->hit_normal = hit_normal;
        compositor->hit_texcoords = text_coords;

        if (compositor_is_composite_texture(tr_state->appear)) {
                compositor->hit_appear = tr_state->appear;
        } else {
                compositor->hit_appear = NULL;
        }
        compositor->hit_node = n;
        compositor->hit_use_dom_events = 0;
        GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Picking] node %s (def %s) is under mouse - hit %g %g %g\n", gf_node_get_class_name(n), gf_node_get_name(n),
                                              FIX2FLT(world_pt.x), FIX2FLT(world_pt.y), FIX2FLT(world_pt.z)));
}


void visual_3d_vrml_drawable_collide(GF_Node *node, GF_TraverseState *tr_state)
{
        SFVec3f pos, v1, v2, collide_pt, last_pos;
        Fixed dist, m_dist;
        GF_Matrix mx;
        u32 cull_backup;
        Drawable3D *st = (Drawable3D *)gf_node_get_private(node);
        if (!st || !st->mesh) return;

        /*no collision with lines & points*/
        if (st->mesh->mesh_type != MESH_TRIANGLES) return;

#ifndef GPAC_DISABLE_VRML
        /*no collision with text (vrml)*/
        switch (gf_node_get_tag(node)) {
        case TAG_MPEG4_Text:
#ifndef GPAC_DISABLE_X3D
        case TAG_X3D_Text:
#endif
                return;
        }
#endif

        /*cull but don't use near plane to detect objects behind us*/
        cull_backup = tr_state->cull_flag;
        if (!visual_3d_node_cull(tr_state, &st->mesh->bounds, 1)) {
                tr_state->cull_flag = cull_backup;
                return;
        }
        tr_state->cull_flag = cull_backup;

        /*use up & front to get an average size of the collision dist in this space*/
        pos = tr_state->camera->position;
        last_pos = tr_state->camera->last_pos;
        v1 = camera_get_target_dir(tr_state->camera);
        v1 = gf_vec_scale(v1, tr_state->camera->avatar_size.x);
        gf_vec_add(v1, v1, pos);
        v2 = camera_get_right_dir(tr_state->camera);
        v2 = gf_vec_scale(v2, tr_state->camera->avatar_size.x);
        gf_vec_add(v2, v2, pos);

        gf_mx_copy(mx, tr_state->model_matrix);
        gf_mx_inverse(&mx);

        gf_mx_apply_vec(&mx, &pos);
        gf_mx_apply_vec(&mx, &last_pos);
        gf_mx_apply_vec(&mx, &v1);
        gf_mx_apply_vec(&mx, &v2);

        gf_vec_diff(v1, v1, pos);
        gf_vec_diff(v2, v2, pos);
        dist = gf_vec_len(v1);
        m_dist = gf_vec_len(v2);
        if (dist<m_dist) m_dist = dist;

        /*check for any collisions*/
        if (gf_mesh_closest_face(st->mesh, pos, m_dist, &collide_pt)) {
                /*get transformed hit*/
                gf_mx_apply_vec(&tr_state->model_matrix, &collide_pt);
                gf_vec_diff(v2, tr_state->camera->position, collide_pt);
                dist = gf_vec_len(v2);
                if (dist<tr_state->camera->collide_dist) {
                        tr_state->camera->collide_dist = dist;
                        tr_state->camera->collide_flags |= CF_COLLISION;
                        tr_state->camera->collide_point = collide_pt;

#ifndef GPAC_DISABLE_LOG
                        if (gf_log_tool_level_on(GF_LOG_COMPOSE, GF_LOG_DEBUG)) {
                                gf_vec_diff(v1, pos, collide_pt);
                                gf_vec_norm(&v1);
                                GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Collision] found at %g %g %g (WC) - dist (%g) - local normal %g %g %g\n",
                                                                      FIX2FLT(tr_state->camera->collide_point.x), FIX2FLT(tr_state->camera->collide_point.y), FIX2FLT(tr_state->camera->collide_point.z),
                                                                      FIX2FLT(dist),
                                                                      FIX2FLT(v1.x), FIX2FLT(v1.y), FIX2FLT(v1.z)));
                        }
#endif
                }
                else {
                        GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Collision] Existing collision (dist %g) closer than current collsion (dist %g)\n", FIX2FLT(tr_state->camera->collide_dist), FIX2FLT(dist) ));
                }
        }

        if (tr_state->camera->collide_flags & CF_DO_GRAVITY) {
                GF_Ray r;
                Bool intersect;
                r.orig = tr_state->camera->position;
                r.dir = gf_vec_scale(tr_state->camera->up, -FIX_ONE);
                gf_mx_apply_ray(&mx, &r);

                intersect = gf_mesh_intersect_ray(st->mesh, &r, &collide_pt, &v1, NULL);

                if (intersect) {
                        gf_mx_apply_vec(&tr_state->model_matrix, &collide_pt);
                        gf_vec_diff(v2, tr_state->camera->position, collide_pt);
                        dist = gf_vec_len(v2);
                        if (dist<tr_state->camera->ground_dist) {
                                tr_state->camera->ground_dist = dist;
                                tr_state->camera->collide_flags |= CF_GRAVITY;
                                tr_state->camera->ground_point = collide_pt;
                                GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Collision] Ground found at %g %g %g (WC) - dist %g - local normal %g %g %g\n",
                                                                      FIX2FLT(tr_state->camera->ground_point.x), FIX2FLT(tr_state->camera->ground_point.y), FIX2FLT(tr_state->camera->ground_point.z),
                                                                      FIX2FLT(dist),
                                                                      FIX2FLT(v1.x), FIX2FLT(v1.y), FIX2FLT(v1.z)));
                        }
                        else {
                                GF_LOG(GF_LOG_DEBUG, GF_LOG_COMPOSE, ("[Collision] Existing ground (dist %g) closer than current (dist %g)\n", FIX2FLT(tr_state->camera->ground_dist), FIX2FLT(dist)));
                        }
                }
        }
}

#endif /*GPAC_DISABLE_VRML*/


static GF_TextureHandler *visual_3d_setup_texture_2d(GF_TraverseState *tr_state, DrawAspect2D *asp, GF_Mesh *mesh)
{
        if (!asp->fill_texture) return NULL;

        if (asp->fill_color && (GF_COL_A(asp->fill_color) != 0xFF)) {
                visual_3d_set_material_2d_argb(tr_state->visual, asp->fill_color);
                gf_sc_texture_set_blend_mode(asp->fill_texture, TX_MODULATE);
        } else {
                visual_3d_set_state(tr_state->visual, V3D_STATE_BLEND, 0);
                gf_sc_texture_set_blend_mode(asp->fill_texture, TX_REPLACE);
        }

        if (asp->fill_texture->flags & GF_SR_TEXTURE_SVG) {
                GF_Rect rc;
                gf_rect_from_bbox(&rc, &mesh->bounds);
                tr_state->mesh_num_textures = gf_sc_texture_enable_ex(asp->fill_texture, NULL, &rc);
        } else {
#ifndef GPAC_DISABLE_VRML
                tr_state->mesh_num_textures = gf_sc_texture_enable(asp->fill_texture, tr_state->appear ? ((M_Appearance *)tr_state->appear)->textureTransform : NULL);
#endif
        }
        if (tr_state->mesh_num_textures) return asp->fill_texture;
        return NULL;
}

void visual_3d_set_2d_strike(GF_TraverseState *tr_state, DrawAspect2D *asp)
{
        if (asp->line_texture) {
                GF_Node *txtrans = NULL;
#ifndef GPAC_DISABLE_VRML
                if (tr_state->appear
                        && (gf_node_get_tag( ((M_Appearance *)tr_state->appear)->material) == TAG_MPEG4_Material2D)
                        && (gf_node_get_tag(((M_Material2D *) ((M_Appearance *)tr_state->appear)->material)->lineProps) == TAG_MPEG4_XLineProperties)
                   ) {
                        txtrans = ((M_XLineProperties *) ((M_Material2D *) ((M_Appearance *)tr_state->appear)->material)->lineProps)->textureTransform;
                }
#endif

                /*We forgot to specify this in the spec ...*/
                gf_sc_texture_set_blend_mode(asp->line_texture, TX_REPLACE);
#if 0
                if (asp->line_alpha != FIX_ONE) {
                        visual_3d_set_material_2d(tr_state->visual, asp->line_color, asp->line_alpha);
                        gf_sc_texture_set_blend_mode(asp->txh, TX_MODULATE);
                } else {
                        visual_3d_set_state(tr_state->visual, V3D_STATE_BLEND, 0);
                        gf_sc_texture_set_blend_mode(asp->txh, TX_REPLACE);
                }
#endif
                tr_state->mesh_num_textures = gf_sc_texture_enable(asp->line_texture, txtrans);
                if (tr_state->mesh_num_textures) return;
        }
        /*no texture or not ready, use color*/
        if (asp->line_color)
                visual_3d_set_material_2d_argb(tr_state->visual, asp->line_color);
}


void visual_3d_draw_2d_with_aspect(Drawable *st, GF_TraverseState *tr_state, DrawAspect2D *asp)
{
        StrikeInfo2D *si;
        GF_TextureHandler *fill_txh;

        fill_txh = visual_3d_setup_texture_2d(tr_state, asp, st->mesh);

        /*fill path*/
        if (fill_txh || (GF_COL_A(asp->fill_color)) ) {
                if (!st->mesh) return;

                if (asp->fill_color)
                        visual_3d_set_material_2d_argb(tr_state->visual, asp->fill_color);
                else if (GF_COL_A(asp->line_color) && !(asp->line_color & 0x00FFFFFF)) {
                        u32 col = asp->line_color | 0x00FFFFFF;
                        visual_3d_set_material_2d_argb(tr_state->visual, col);
                }

                visual_3d_mesh_paint(tr_state, st->mesh);
                /*reset texturing in case of line texture*/
                if (tr_state->mesh_num_textures) {
                        gf_sc_texture_disable(fill_txh);
                        tr_state->mesh_num_textures = 0;
                }
        }

        if ((tr_state->visual->type_3d == 4) && !asp->line_texture) return;

        /*strike path*/
        if (!asp->pen_props.width || !GF_COL_A(asp->line_color)) return;

        si = drawable_get_strikeinfo(tr_state->visual->compositor, st, asp, tr_state->appear, NULL, 0, tr_state);
        if (!si) return;

        if (!si->mesh_outline) {
                si->is_vectorial = asp->line_texture ? GF_TRUE : !tr_state->visual->compositor->raster_outlines;
                si->mesh_outline = new_mesh();
#ifdef GPAC_HAS_GLU
                if (si->is_vectorial) {
                        gf_mesh_tesselate_path(si->mesh_outline, si->outline, asp->line_texture ? 2 : 1);
                } else
#endif
                        mesh_get_outline(si->mesh_outline, st->path);
        }

        visual_3d_set_2d_strike(tr_state, asp);
        if (asp->line_texture) tr_state->mesh_num_textures = 1;

        if (!si->is_vectorial) {
                visual_3d_mesh_strike(tr_state, si->mesh_outline, asp->pen_props.width, asp->line_scale, asp->pen_props.dash);
        } else {
                visual_3d_mesh_paint(tr_state, si->mesh_outline);
        }
        if (asp->line_texture) {
                gf_sc_texture_disable(asp->line_texture);
                tr_state->mesh_num_textures = 0;
        }
}

#ifndef GPAC_DISABLE_VRML
void visual_3d_draw_2d(Drawable *st, GF_TraverseState *tr_state)
{
        DrawAspect2D asp;
        memset(&asp, 0, sizeof(DrawAspect2D));
        drawable_get_aspect_2d_mpeg4(st->node, &asp, tr_state);
        visual_3d_draw_2d_with_aspect(st, tr_state, &asp);
}
#endif


void visual_3d_draw_from_context(DrawableContext *ctx, GF_TraverseState *tr_state)
{
        GF_Rect rc;
        gf_path_get_bounds(ctx->drawable->path, &rc);
        visual_3d_draw_2d_with_aspect(ctx->drawable, tr_state, &ctx->aspect);

        drawable_check_focus_highlight(ctx->drawable->node, tr_state, &rc);
}


static GFINLINE Bool visual_3d_setup_material(GF_TraverseState *tr_state, u32 mesh_type, Fixed *diffuse_alpha)
{
#ifndef GPAC_DISABLE_VRML
        GF_Node *__mat;
#endif
        SFColor def;
        def.red = def.green = def.blue = FIX_ONE;
        /*store diffuse alpha*/
        if (diffuse_alpha) *diffuse_alpha = FIX_ONE;

        if (!tr_state->appear) {
                /*use material2D to disable lighting*/
                visual_3d_set_material_2d(tr_state->visual, def, FIX_ONE);
                return 1;
        }

#ifndef GPAC_DISABLE_VRML
#ifndef GPAC_DISABLE_X3D
        if (gf_node_get_tag(tr_state->appear)==TAG_X3D_Appearance) {
                X_FillProperties *fp = (X_FillProperties *) ((X_Appearance*)tr_state->appear)->fillProperties;
                if (fp && !fp->filled) return 0;
        }
#endif

        __mat = ((M_Appearance *)tr_state->appear)->material;
        if (!__mat) {
                /*use material2D to disable lighting (cf VRML specs)*/
                visual_3d_set_material_2d(tr_state->visual, def, FIX_ONE);
                return 1;
        }

        switch (gf_node_get_tag((GF_Node *)__mat)) {
        case TAG_MPEG4_Material:
#ifndef GPAC_DISABLE_X3D
        case TAG_X3D_Material:
#endif
        {
                SFColor diff, spec, emi;
                Fixed diff_a, spec_a, emi_a;
                Fixed vec[4];
                Bool has_alpha;
                u32 flag = V3D_STATE_LIGHT /*| V3D_STATE_COLOR*/;
                M_Material *mat = (M_Material *)__mat;

                diff = mat->diffuseColor;
                diff_a = FIX_ONE - mat->transparency;

                /*if drawing in 2D context or special meshes (lines, points) disable lighting*/
                if (mesh_type || !tr_state->camera->is_3D) {
                        if (tr_state->camera->is_3D) diff = mat->emissiveColor;
                        if (!tr_state->color_mat.identity) gf_cmx_apply_fixed(&tr_state->color_mat, &diff_a, &diff.red, &diff.green, &diff.blue);
                        visual_3d_set_material_2d(tr_state->visual, diff, diff_a);
                        return 1;
                }

                spec = mat->specularColor;
                emi = mat->emissiveColor;
                spec_a = emi_a = FIX_ONE - mat->transparency;
                if (!tr_state->color_mat.identity) {
                        gf_cmx_apply_fixed(&tr_state->color_mat, &diff_a, &diff.red, &diff.green, &diff.blue);
                        gf_cmx_apply_fixed(&tr_state->color_mat, &spec_a, &spec.red, &spec.green, &spec.blue);
                        gf_cmx_apply_fixed(&tr_state->color_mat, &emi_a, &emi.red, &emi.green, &emi.blue);

                        if ((diff_a+FIX_EPSILON<FIX_ONE) || (spec_a+FIX_EPSILON<FIX_ONE) || (emi_a+FIX_EPSILON<FIX_ONE )) {
                                has_alpha = 1;
                        } else {
                                has_alpha = 0;
                        }
                } else {
                        has_alpha = (mat->transparency>FIX_EPSILON) ? 1 : 0;
                        /*100% transparent DON'T DRAW*/
                        if (mat->transparency+FIX_EPSILON>=FIX_ONE) return 0;
                }

                /*using antialiasing with alpha usually gives bad results (non-edge face segments are visible)*/
                visual_3d_enable_antialias(tr_state->visual, !has_alpha);
                if (has_alpha) {
                        flag |= V3D_STATE_BLEND;
                        tr_state->mesh_is_transparent = 1;
                }
                visual_3d_set_state(tr_state->visual, flag, 1);

                vec[0] = gf_mulfix(diff.red, mat->ambientIntensity);
                vec[1] = gf_mulfix(diff.green, mat->ambientIntensity);
                vec[2] = gf_mulfix(diff.blue, mat->ambientIntensity);
                vec[3] = diff_a;
                visual_3d_set_material(tr_state->visual, V3D_MATERIAL_AMBIENT, vec);

                vec[0] = diff.red;
                vec[1] = diff.green;
                vec[2] = diff.blue;
                vec[3] = diff_a;
                visual_3d_set_material(tr_state->visual, V3D_MATERIAL_DIFFUSE, vec);

                vec[0] = spec.red;
                vec[1] = spec.green;
                vec[2] = spec.blue;
                vec[3] = spec_a;
                visual_3d_set_material(tr_state->visual, V3D_MATERIAL_SPECULAR, vec);


                vec[0] = emi.red;
                vec[1] = emi.green;
                vec[2] = emi.blue;
                vec[3] = emi_a;
                visual_3d_set_material(tr_state->visual, V3D_MATERIAL_EMISSIVE, vec);

                visual_3d_set_shininess(tr_state->visual, mat->shininess);
                if (diffuse_alpha) *diffuse_alpha = diff_a;
        }
        break;
        case TAG_MPEG4_Material2D:
        {
                SFColor emi;
                Fixed emi_a;
                M_Material2D *mat = (M_Material2D *)__mat;

                emi = mat->emissiveColor;
                emi_a = FIX_ONE - mat->transparency;
                if (!tr_state->color_mat.identity) gf_cmx_apply_fixed(&tr_state->color_mat, &emi_a, &emi.red, &emi.green, &emi.blue);
                /*100% transparent DON'T DRAW*/
                if (emi_a<FIX_EPSILON) return 0;
                else if (emi_a+FIX_EPSILON<FIX_ONE) visual_3d_set_state(tr_state->visual, V3D_STATE_BLEND, 1);


                /*this is an extra feature: if material2D.filled is FALSE on 3D objects, switch to TX_REPLACE mode
                and enable lighting*/
                if (!mat->filled) {
                        if (mat->transparency) {
                                emi.red = emi.green = emi.blue = FIX_ONE;
                        } else {
                                GF_TextureHandler *txh = gf_sc_texture_get_handler(((M_Appearance *)tr_state->appear)->texture);
                                if (txh) {
                                        gf_sc_texture_set_blend_mode(txh, TX_REPLACE);
                                        visual_3d_set_state(tr_state->visual, V3D_STATE_COLOR, 0);
                                        visual_3d_set_state(tr_state->visual, V3D_STATE_LIGHT, 1);
                                        return 1;
                                }
                        }
                }
                /*regular mat 2D*/
                visual_3d_set_state(tr_state->visual, V3D_STATE_LIGHT | V3D_STATE_COLOR, 0);
                visual_3d_set_material_2d(tr_state->visual, emi, emi_a);
        }
        break;
        default:
                break;
        }
        return 1;
#else
        return 0;
#endif  /*GPAC_DISABLE_VRML*/
}

Bool visual_3d_setup_texture(GF_TraverseState *tr_state, Fixed diffuse_alpha)
{
#ifndef GPAC_DISABLE_VRML
        GF_TextureHandler *txh;
        tr_state->mesh_num_textures = 0;
        if (!tr_state->appear) return 0;

        gf_node_dirty_reset(tr_state->appear, 0);

        txh = gf_sc_texture_get_handler(((M_Appearance *)tr_state->appear)->texture);
        if (txh) {
                gf_sc_texture_set_blend_mode(txh, gf_sc_texture_is_transparent(txh) ? TX_MODULATE : TX_REPLACE);
                tr_state->mesh_num_textures = gf_sc_texture_enable(txh, ((M_Appearance *)tr_state->appear)->textureTransform);
                if (tr_state->mesh_num_textures) {
                        Fixed v[4];
                        switch (txh->pixelformat) {
                        /*override diffuse color with full intensity, but keep material alpha (cf VRML lighting)*/
                        case GF_PIXEL_RGB_24:
                                if (tr_state->visual->has_material_2d) {
                                        SFColor c;
                                        c.red = c.green = c.blue = FIX_ONE;
                                        visual_3d_set_material_2d(tr_state->visual, c, diffuse_alpha);
                                } else {
                                        v[0] = v[1] = v[2] = FIX_ONE;
                                        v[3] = diffuse_alpha;
                                        visual_3d_set_material(tr_state->visual, V3D_MATERIAL_DIFFUSE, v);
                                }
                                break;
                        /*override diffuse color AND material alpha (cf VRML lighting)*/
                        case GF_PIXEL_RGBA:
                                if (!tr_state->visual->has_material_2d) {
                                        v[0] = v[1] = v[2] = v[3] = FIX_ONE;
                                        visual_3d_set_material(tr_state->visual, V3D_MATERIAL_DIFFUSE, v);
                                }
                                tr_state->mesh_is_transparent = 1;
                                break;
                                /*                      case GF_PIXEL_GREYSCALE:
                                                                tr_state->mesh_num_textures = 2;
                                                                break;
                                */
                        }
                }
                return tr_state->mesh_num_textures;
        }
#endif /*GPAC_DISABLE_VRML*/
        return 0;
}

void visual_3d_disable_texture(GF_TraverseState *tr_state)
{
        if (tr_state->mesh_num_textures) {
#ifndef GPAC_DISABLE_VRML
                gf_sc_texture_disable(gf_sc_texture_get_handler( ((M_Appearance *)tr_state->appear)->texture) );
#endif
                tr_state->mesh_num_textures = 0;
        }
}

Bool visual_3d_setup_appearance(GF_TraverseState *tr_state)
{
        Fixed diff_a;
        /*setup material and check if 100% transparent - in which case don't draw*/
        if (!visual_3d_setup_material(tr_state, 0, &diff_a)) return 0;
        /*setup texture*/
        visual_3d_setup_texture(tr_state, diff_a);
        return 1;
}


void visual_3d_draw(GF_TraverseState *tr_state, GF_Mesh *mesh)
{
        if (mesh->mesh_type) {
                if (visual_3d_setup_material(tr_state, mesh->mesh_type, NULL)) {
                        visual_3d_mesh_paint(tr_state, mesh);
                }
        } else if (visual_3d_setup_appearance(tr_state)) {
                visual_3d_mesh_paint(tr_state, mesh);
                visual_3d_disable_texture(tr_state);

#if !defined(GPAC_DISABLE_VRML) && !defined(GPAC_USE_GLES1X) && !defined(GPAC_USE_TINYGL) && !defined(GPAC_DISABLE_X3D) && !defined(GPAC_USE_GLES2)
                if (tr_state->appear && gf_node_get_tag(tr_state->appear)==TAG_X3D_Appearance) {
                        X_Appearance *ap = (X_Appearance *)tr_state->appear;
                        X_FillProperties *fp = ap->fillProperties ? (X_FillProperties *) ap->fillProperties : NULL;
                        if (fp && fp->hatched) visual_3d_mesh_hatch(tr_state, mesh, fp->hatchStyle, fp->hatchColor);
                }
#endif
        }
}

void visual_3d_projection_matrix_modified(GF_VisualManager *visual)
{
        visual->needs_projection_matrix_reload = 1;
}

void visual_3d_enable_headlight(GF_VisualManager *visual, Bool bOn, GF_Camera *cam)
{
        SFVec3f dir;
        SFColor col;

        if (!bOn) return;
        //if we have lights in the scene don't turn the headlight on
        if (visual->has_inactive_lights || visual->num_lights) return;

        col.blue = col.red = col.green = FIX_ONE;
        dir.x = dir.y = 0;
        dir.z = -FIX_ONE;
//      if (cam->is_3D) dir = camera_get_target_dir(cam);
//      visual_3d_add_directional_light(visual, 0, col, FIX_ONE, dir, &cam->modelview);

        visual_3d_add_directional_light(visual, 0, col, FIX_ONE, dir, NULL);
}

void visual_3d_set_material_2d(GF_VisualManager *visual, SFColor col, Fixed alpha)
{
        visual->has_material_2d = alpha ? 1 : 0;
        visual->has_material=0;
        if (visual->has_material_2d) {
                visual->mat_2d.red = col.red;
                visual->mat_2d.green = col.green;
                visual->mat_2d.blue = col.blue;
                visual->mat_2d.alpha = alpha;
        }

}

void visual_3d_set_material_2d_argb(GF_VisualManager *visual, u32 col)
{
        u32 a = GF_COL_A(col);
        visual->has_material_2d = a ? 1 : 0;
        visual->has_material=0;
        if (visual->has_material_2d) {
                visual->mat_2d.red = INT2FIX( GF_COL_R(col) ) / 255;
                visual->mat_2d.green = INT2FIX( GF_COL_G(col) ) / 255;
                visual->mat_2d.blue = INT2FIX( GF_COL_B(col) ) / 255;
                visual->mat_2d.alpha = INT2FIX( a ) / 255;;
        }
}

void visual_3d_set_clipper_2d(GF_VisualManager *visual, GF_Rect clip, GF_Matrix *mx_at_clipper)
{
        if (mx_at_clipper)
                gf_mx_apply_rect(mx_at_clipper, &clip);
        visual->clipper_2d = gf_rect_pixelize(&clip);
        visual->has_clipper_2d = GF_TRUE;
}

void visual_3d_reset_clipper_2d(GF_VisualManager *visual)
{
        visual->has_clipper_2d = GF_FALSE;
}

void visual_3d_set_clip_plane(GF_VisualManager *visual, GF_Plane p, GF_Matrix *mx_at_clipper, Bool is_2d_clip)
{
        if (visual->num_clips==GF_MAX_GL_CLIPS) return;
        gf_vec_norm(&p.normal);
        visual->clippers[visual->num_clips].p = p;
        visual->clippers[visual->num_clips].is_2d_clip = is_2d_clip;
        visual->clippers[visual->num_clips].mx_clipper = mx_at_clipper;
        visual->num_clips++;
}

void visual_3d_reset_clip_plane(GF_VisualManager *visual)
{
        if (!visual->num_clips) return;
        visual->num_clips -= 1;
}

void visual_3d_set_material(GF_VisualManager *visual, u32 material_type, Fixed *rgba)
{
        visual->materials[material_type].red = rgba[0];
        visual->materials[material_type].green = rgba[1];
        visual->materials[material_type].blue = rgba[2];
        visual->materials[material_type].alpha = rgba[3];

        visual->has_material = 1;
        visual->has_material_2d=0;
}

void visual_3d_set_shininess(GF_VisualManager *visual, Fixed shininess)
{
        visual->shininess = shininess;
}

void visual_3d_set_state(GF_VisualManager *visual, u32 flag_mask, Bool setOn)
{
        if (flag_mask & V3D_STATE_LIGHT) visual->state_light_on = setOn;
        if (flag_mask & V3D_STATE_BLEND) visual->state_blend_on = setOn;
        if (flag_mask & V3D_STATE_COLOR) visual->state_color_on = setOn;
}


void visual_3d_set_texture_matrix(GF_VisualManager *visual, GF_Matrix *mx)
{
        visual->has_tx_matrix = mx ? 1 : 0;
        if (mx) gf_mx_copy(visual->tx_matrix, *mx);
}

void visual_3d_has_inactive_light(GF_VisualManager *visual)
{
        visual->has_inactive_lights = GF_TRUE;
}

Bool visual_3d_add_point_light(GF_VisualManager *visual, Fixed ambientIntensity, SFVec3f attenuation, SFColor color, Fixed intensity, SFVec3f location, GF_Matrix *light_mx)
{
        if (visual->num_lights==visual->max_lights) return 0;
        visual->lights[visual->num_lights].type = 2;
        visual->lights[visual->num_lights].ambientIntensity = ambientIntensity;
        visual->lights[visual->num_lights].attenuation = attenuation;
        visual->lights[visual->num_lights].color = color;
        visual->lights[visual->num_lights].intensity = intensity;
        visual->lights[visual->num_lights].position = location;
        memcpy(&visual->lights[visual->num_lights].light_mx, light_mx, sizeof(GF_Matrix) );
        visual->num_lights++;
        return 1;
}

Bool visual_3d_add_spot_light(GF_VisualManager *visual, Fixed ambientIntensity, SFVec3f attenuation, Fixed beamWidth,
                              SFColor color, Fixed cutOffAngle, SFVec3f direction, Fixed intensity, SFVec3f location, GF_Matrix *light_mx)
{
        if (visual->num_lights==visual->max_lights) return 0;
        visual->lights[visual->num_lights].type = 1;
        visual->lights[visual->num_lights].ambientIntensity = ambientIntensity;
        visual->lights[visual->num_lights].attenuation = attenuation;
        visual->lights[visual->num_lights].beamWidth = beamWidth;
        visual->lights[visual->num_lights].cutOffAngle = cutOffAngle;
        visual->lights[visual->num_lights].color = color;
        visual->lights[visual->num_lights].direction = direction;
        visual->lights[visual->num_lights].intensity = intensity;
        visual->lights[visual->num_lights].position = location;
        memcpy(&visual->lights[visual->num_lights].light_mx, light_mx, sizeof(GF_Matrix) );
        visual->num_lights++;
        return 1;
}

Bool visual_3d_add_directional_light(GF_VisualManager *visual, Fixed ambientIntensity, SFColor color, Fixed intensity, SFVec3f direction, GF_Matrix *light_mx)
{
        if (visual->num_lights==visual->max_lights) return 0;
        visual->lights[visual->num_lights].type = 0;
        visual->lights[visual->num_lights].ambientIntensity = ambientIntensity;
        visual->lights[visual->num_lights].color = color;
        visual->lights[visual->num_lights].intensity = intensity;
        visual->lights[visual->num_lights].direction = direction;
        if (light_mx) {
                memcpy(&visual->lights[visual->num_lights].light_mx, light_mx, sizeof(GF_Matrix) );
        } else {
                gf_mx_init(visual->lights[visual->num_lights].light_mx);
                visual->lights[visual->num_lights].type = 3;
                visual->lights[visual->num_lights].direction.x = 0;
                visual->lights[visual->num_lights].direction.y = 0;
                visual->lights[visual->num_lights].direction.z = -FIX_ONE;
        }

        visual->num_lights++;
        return 1;
}


void visual_3d_remove_last_light(GF_VisualManager *visual)
{
        if (visual->num_lights) {
                visual->num_lights--;
        }
}

void visual_3d_clear_all_lights(GF_VisualManager *visual)
{
        visual->num_lights = 0;
        visual->has_inactive_lights = GF_FALSE;
}

void visual_3d_set_fog(GF_VisualManager *visual, const char *type, SFColor color, Fixed density, Fixed visibility)
{
        visual->has_fog = GF_TRUE;
        if (!type || !stricmp(type, "LINEAR")) visual->fog_type = 0;
        else if (!stricmp(type, "EXPONENTIAL")) visual->fog_type = 1;
        else if (!stricmp(type, "EXPONENTIAL2")) visual->fog_type = 2;

        visual->fog_color = color;
        visual->fog_density = density;
        visual->fog_visibility = visibility;
}

#endif


/* [<][>][^][v][top][bottom][index][help] */