root/src/compositor/mpeg4_grouping.c

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

DEFINITIONS

This source file includes following definitions.
  1. group_2d_traverse
  2. group_2d_traverse_with_order
  3. group_3d_setup
  4. group_3d_delete
  5. group_3d_new
  6. get_light_type
  7. group_3d_traverse
  8. parent_node_setup
  9. parent_node_predestroy
  10. parent_node_reset
  11. parent_node_start_group
  12. parent_node_end_group
  13. parent_node_end_text_group
  14. parent_node_traverse
  15. parent_node_child_traverse
  16. parent_node_child_traverse_matrix

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

#ifndef GPAC_DISABLE_VRML

/*This is the generic routine for child traversing*/
void group_2d_traverse(GF_Node *node, GroupingNode2D *group, GF_TraverseState *tr_state)
{
        u32 backup;
#ifdef GF_SR_USE_VIDEO_CACHE
        Bool group_cached;
#endif
        GF_List *sensor_backup;
        GF_ChildNodeItem *child;

        backup = gf_node_dirty_get(node);
        if (backup & GF_SG_CHILD_DIRTY) {
                GF_SensorHandler *hsens;
                Bool check_anchor=0;
                u32 ntag = gf_node_get_tag(node);
                group->flags &= ~GROUP_HAS_SENSORS;
                if (group->sensors) gf_list_reset(group->sensors);

                drawable_reset_group_highlight(tr_state, node);

                /*never performs bounds recompute on the fly in 2D since we don't cull 2D groups
                but still mark the group as empty*/
                group->bounds.width = 0;
                /*special case for anchor which is a parent node acting as a sensor*/
                if (ntag==TAG_MPEG4_Anchor) check_anchor=1;
#ifndef GPAC_DISABLE_X3D
                else if (ntag==TAG_X3D_Anchor) check_anchor=1;
#endif
                if (check_anchor) {
                        GF_SensorHandler *gf_sc_anchor_get_handler(GF_Node *n);

                        hsens = gf_sc_anchor_get_handler(node);
                        if (hsens) {
                                if (!group->sensors) group->sensors = gf_list_new();
                                gf_list_add(group->sensors, hsens);
                                group->flags |= GROUP_HAS_SENSORS | GROUP_IS_ANCHOR;
                        }
                } else {
                        child = ((GF_ParentNode *)node)->children;
                        while (child) {
                                hsens = compositor_mpeg4_get_sensor_handler_ex(child->node, GF_TRUE);
                                if (hsens) {
                                        if (!group->sensors) group->sensors = gf_list_new();
                                        gf_list_add(group->sensors, hsens);
                                        group->flags |= GROUP_HAS_SENSORS;
                                }
                                child = child->next;
                        }
                }
        }
        /*sub-tree not dirty and getting bounds, direct copy */
        else if ((tr_state->traversing_mode==TRAVERSE_GET_BOUNDS) && !tr_state->for_node && group->bounds.width) {
                tr_state->bounds = group->bounds;
                return;
        }


#ifdef GF_SR_USE_VIDEO_CACHE
        group_cached = group_2d_cache_traverse(node, group, tr_state);
#endif

        /*now here is the trick: ExternProtos may not be loaded at this point, in which case we can't
        perform proper culling. Unloaded ExternProto signal themselves by invalidating their parent
        graph to get a new traversal. We must therefore reset the CHILD_DIRTY flag before computing
        bounds otherwise we'll never re-invalidate the subgraph anymore*/
        gf_node_dirty_clear(node, GF_SG_CHILD_DIRTY);


#ifdef GF_SR_USE_VIDEO_CACHE
        if (group_cached) return;
#endif

        /*no culling in 2d*/

        /*picking: collect sensors*/
        sensor_backup = NULL;
        if ((tr_state->traversing_mode==TRAVERSE_PICK) && (group->flags & GROUP_HAS_SENSORS) ) {
                /*reset sensor stack if any sensors at this level*/
                sensor_backup = tr_state->vrml_sensors;
                assert(group->sensors);
                tr_state->vrml_sensors = group->sensors;
        }


        if (tr_state->traversing_mode==TRAVERSE_GET_BOUNDS) {
                child = ((GF_ParentNode *)node)->children;
                backup = tr_state->text_split_mode;
                if (tr_state->text_split_mode && (gf_node_list_get_count(child)>1) ) tr_state->text_split_mode = 0;
                group->flags &= ~GROUP_SKIP_CULLING;
                group->bounds.width = group->bounds.height = 0;
                tr_state->bounds.width = tr_state->bounds.height = 0;
#ifndef GPAC_DISABLE_3D
                tr_state->bbox.is_set = 0;
#endif
                while (child) {
                        gf_node_traverse(child->node, tr_state);
                        if (tr_state->disable_cull) {
                                group->flags |= GROUP_SKIP_CULLING;
                                tr_state->disable_cull = 0;
                        }
                        /*handle 3D nodes in 2D groups*/
#ifndef GPAC_DISABLE_3D
                        if (tr_state->bbox.is_set) {
                                gf_rect_from_bbox(&tr_state->bounds, &tr_state->bbox);
                                tr_state->bbox.is_set = 0;
                        }
#endif
                        gf_rect_union(&group->bounds, &tr_state->bounds);
                        tr_state->bounds.width = tr_state->bounds.height = 0;
                        child = child->next;
                }

                tr_state->bounds = group->bounds;

                if (group->flags & GROUP_SKIP_CULLING)
                        tr_state->disable_cull = 1;
                tr_state->text_split_mode = backup;
        }
        /*TRAVERSE_SORT */
        else if (tr_state->traversing_mode==TRAVERSE_SORT) {
                Bool prev_inv = tr_state->invalidate_all;
#ifdef GF_SR_USE_VIDEO_CACHE
                DrawableContext *first_ctx = tr_state->visual->cur_context;
                Bool skip_first_ctx = (first_ctx && first_ctx->drawable) ? 1 : 0;
                u32 cache_too_small = 0;
                u32 traverse_time = gf_sys_clock();
                u32 last_cache_idx = gf_list_count(tr_state->visual->compositor->cached_groups_queue);
                tr_state->cache_too_small = 0;
#endif

                if (backup & GF_SG_VRML_COLOR_DIRTY) {
                        tr_state->invalidate_all = 1;
                        gf_node_dirty_clear(node, GF_SG_VRML_COLOR_DIRTY);
                }

                child = ((GF_ParentNode *)node)->children;
                while (child) {
                        gf_node_traverse(child->node, tr_state);
                        child = child->next;
#ifdef GF_SR_USE_VIDEO_CACHE
                        if (tr_state->cache_too_small)
                                cache_too_small++;
#endif
                }

                tr_state->invalidate_all = prev_inv;

#ifdef GF_SR_USE_VIDEO_CACHE
                if (cache_too_small) {
                        tr_state->cache_too_small = 1;
                } else {
                        /*get the traversal time for each group*/
                        traverse_time = gf_sys_clock() - traverse_time;
                        group->traverse_time += traverse_time;
                        /*record the traversal information and turn cache on if possible*/
                        group_2d_cache_evaluate(node, group, tr_state, first_ctx, skip_first_ctx, last_cache_idx);
                }
#endif

                drawable_check_focus_highlight(node, tr_state, NULL);
        }
        else {
                child = ((GF_ParentNode *)node)->children;
                while (child) {
                        gf_node_traverse(child->node, tr_state);
                        child = child->next;
                }

        }

        if (sensor_backup) {
                /*restore previous traversing state sensors */
                tr_state->vrml_sensors = sensor_backup;
        }
}

/*This is the routine for OrderedGroup child traversing*/
void group_2d_traverse_with_order(GF_Node *node, GroupingNode2D *group, GF_TraverseState *tr_state, u32 *positions)
{
        u32 i, count;
        Bool backup;
        GF_List *sensor_backup;
        GF_Node *child;
        GF_ChildNodeItem *list;
#ifdef GF_SR_USE_VIDEO_CACHE
        Bool group_cached;
#endif

        backup = gf_node_dirty_get(node);
        if (backup & GF_SG_CHILD_DIRTY) {
                GF_SensorHandler *hsens;
                Bool check_anchor=0;
                /*never trigger bounds recompute in 2D since we don't cull 2D groups*/
                u32 ntag = gf_node_get_tag(node);
                group->flags &= ~GROUP_HAS_SENSORS;
                drawable_reset_group_highlight(tr_state, node);
                /*special case for anchor which is a parent node acting as a sensor*/
                if (ntag==TAG_MPEG4_Anchor) check_anchor=1;
#ifndef GPAC_DISABLE_X3D
                else if (ntag==TAG_X3D_Anchor) check_anchor=1;
#endif
                if (check_anchor) {
                        GF_SensorHandler *gf_sc_anchor_get_handler(GF_Node *n);

                        hsens = gf_sc_anchor_get_handler(node);
                        if (hsens) {
                                if (!group->sensors) group->sensors = gf_list_new();
                                gf_list_add(group->sensors, hsens);
                                group->flags |= GROUP_HAS_SENSORS | GROUP_IS_ANCHOR;
                        }
                } else {
                        list = ((GF_ParentNode *)node)->children;
                        while (list) {
                                hsens = compositor_mpeg4_get_sensor_handler_ex(list->node, GF_TRUE);
                                if (hsens) {
                                        if (!group->sensors) group->sensors = gf_list_new();
                                        gf_list_add(group->sensors, hsens);
                                        group->flags |= GROUP_HAS_SENSORS;
                                }
                                list = list->next;
                        }
                }
        }
        /*not parent (eg form, layout...) sub-tree not dirty and getting bounds, direct copy */
        else if (tr_state->traversing_mode==TRAVERSE_GET_BOUNDS) {
                tr_state->bounds = group->bounds;
                return;
        }

#ifdef GF_SR_USE_VIDEO_CACHE
        group_cached = group_2d_cache_traverse(node, group, tr_state);
#endif

        /*now here is the trick: ExternProtos may not be loaded at this point, in which case we can't
        perform proper culling. Unloaded ExternProto signal themselves by invalidating their parent
        graph to get a new traversal. We must therefore reset the CHILD_DIRTY flag before computing
        bounds otherwise we'll never re-invalidate the subgraph anymore*/
        gf_node_dirty_clear(node, GF_SG_CHILD_DIRTY);


#ifdef GF_SR_USE_VIDEO_CACHE
        if (group_cached) return;
#endif

        /*picking: collect sensors*/
        sensor_backup = NULL;
        if ((tr_state->traversing_mode==TRAVERSE_PICK) && (group->flags & GROUP_HAS_SENSORS) ) {
                /*reset sensor stack if any sensors at this level*/
                sensor_backup = tr_state->vrml_sensors;
                tr_state->vrml_sensors = group->sensors;
        }

        if (tr_state->traversing_mode==TRAVERSE_GET_BOUNDS) {
                list = ((GF_ParentNode *)node)->children;
                backup = tr_state->text_split_mode;
                if (tr_state->text_split_mode && (gf_node_list_get_count(list)>1) ) tr_state->text_split_mode = 0;
                group->flags &= ~GROUP_SKIP_CULLING;
                group->bounds.width = group->bounds.height = 0;
                tr_state->bounds.width = tr_state->bounds.height = 0;
#ifndef GPAC_DISABLE_3D
                tr_state->bbox.is_set = 0;
#endif
                count = gf_node_list_get_count(list);
                for (i=0; i<count; i++) {
                        child = gf_node_list_get_child(list, positions[i]);
                        gf_node_traverse(child, tr_state);
                        if (tr_state->disable_cull) {
                                group->flags |= GROUP_SKIP_CULLING;
                                tr_state->disable_cull = 0;
                        }
                        /*handle 3D nodes in 2D groups*/
#ifndef GPAC_DISABLE_3D
                        if (tr_state->bbox.is_set) {
                                gf_rect_from_bbox(&tr_state->bounds, &tr_state->bbox);
                                tr_state->bbox.is_set = 0;
                        }
#endif
                        gf_rect_union(&group->bounds, &tr_state->bounds);
                        tr_state->bounds.width = tr_state->bounds.height = 0;
                }
                tr_state->bounds = group->bounds;

                if (group->flags & GROUP_SKIP_CULLING)
                        tr_state->disable_cull = 1;
                tr_state->text_split_mode = backup;

                /*TRAVERSE_SORT */
        } else if (tr_state->traversing_mode==TRAVERSE_SORT) {
                Bool prev_inv = tr_state->invalidate_all;
#ifdef GF_SR_USE_VIDEO_CACHE
                DrawableContext *first_ctx = tr_state->visual->cur_context;
                u32 cache_too_small = 0;
                Bool skip_first_ctx = (first_ctx && first_ctx->drawable) ? 1 : 0;
                u32 traverse_time = gf_sys_clock();
                u32 last_cache_idx = gf_list_count(tr_state->visual->compositor->cached_groups_queue);
                tr_state->cache_too_small = 0;
#endif

                if (backup & GF_SG_VRML_COLOR_DIRTY) {
                        tr_state->invalidate_all = 1;
                        gf_node_dirty_clear(node, GF_SG_VRML_COLOR_DIRTY);
                }

                list = ((GF_ParentNode *)node)->children;
                count = gf_node_list_get_count(list);
                for (i=0; i<count; i++) {
                        child = gf_node_list_get_child(list, positions[i]);
                        gf_node_traverse(child, tr_state);
#ifdef GF_SR_USE_VIDEO_CACHE
                        if (tr_state->cache_too_small)
                                cache_too_small++;
#endif
                }
                tr_state->invalidate_all = prev_inv;

#ifdef GF_SR_USE_VIDEO_CACHE
                if (cache_too_small) {
                        tr_state->cache_too_small = 1;
                } else {
                        /*get the traversal time for each group*/
                        traverse_time = gf_sys_clock() - traverse_time;
                        group->traverse_time += traverse_time;
                        /*record the traversal information and turn cache on if possible*/
                        group_2d_cache_evaluate(node, group, tr_state, first_ctx, skip_first_ctx, last_cache_idx);
                }
#endif

                drawable_check_focus_highlight(node, tr_state, NULL);
        } else {
                list = ((GF_ParentNode *)node)->children;
                count = gf_node_list_get_count(list);
                for (i=0; i<count; i++) {
                        child = gf_node_list_get_child(list, positions[i]);
                        gf_node_traverse(child, tr_state);
                }
        }

        if (sensor_backup) {
                /*restore previous traversing state sensors*/
                tr_state->vrml_sensors = sensor_backup;
        }
}

/*
 *      3D Grouping tools
 */

#ifndef GPAC_DISABLE_3D

void group_3d_setup(GroupingNode *group, GF_Node *node)
{
        memset(group, 0, sizeof(GroupingNode));
}

void group_3d_delete(GF_Node *node)
{
        GroupingNode *group = (GroupingNode *)gf_node_get_private(node);

        gf_free(group);
}

GroupingNode *group_3d_new(GF_Node *node)
{
        GroupingNode *st;
        GF_SAFEALLOC(st, GroupingNode);
        gf_node_set_private(node, st);
        return st;
}

/*returns 2 if local light, 1 if global light and 0 if not a light*/
static u32 get_light_type(GF_Node *n)
{
        switch (gf_node_get_tag(n)) {
        case TAG_MPEG4_DirectionalLight:
#ifndef GPAC_DISABLE_X3D
        case TAG_X3D_DirectionalLight:
#endif
                return 2;
        case TAG_MPEG4_PointLight:
        case TAG_MPEG4_SpotLight:
                return 1;
        default:
                return 0;
        }
}
/*This is the generic routine for child traversing*/
void group_3d_traverse(GF_Node *node, GroupingNode *group, GF_TraverseState *tr_state)
{
        u32 mode_back;
        Bool split_text_backup, do_lights;
        DirectionalLightContext *dl;
        GF_List *sensor_backup;
        GF_SensorHandler *hsens;
        GF_ChildNodeItem *l;

        if (gf_node_dirty_get(node) & GF_SG_CHILD_DIRTY) {
                //we are drawing 3D object but configured for 2D, force 3D
                if (!tr_state->visual->type_3d && tr_state->visual->compositor->hybrid_opengl) {
                        tr_state->visual->compositor->root_visual_setup=0;
                        tr_state->visual->compositor->force_type_3d=1;
                }
                
                /*need to recompute bounds*/
                if (tr_state->traversing_mode!=TRAVERSE_GET_BOUNDS) {
                        /*traverse subtree to recompute bounds*/
                        mode_back = tr_state->traversing_mode;
                        tr_state->traversing_mode=TRAVERSE_GET_BOUNDS;
                        group_3d_traverse(node, group, tr_state);
                        tr_state->traversing_mode = mode_back;
                }
                /*we're recomputing bounds*/
                else {
                        u32 ntag = gf_node_get_tag(node);
                        group->flags &= ~(GROUP_HAS_SENSORS | GROUP_HAS_LIGHTS);

                        /*special case for anchor which is a parent node acting as a sensor*/
                        if ((ntag==TAG_MPEG4_Anchor)
#ifndef GPAC_DISABLE_X3D
                                || (ntag==TAG_X3D_Anchor)
#endif
                           ) group->flags |= GROUP_HAS_SENSORS;

                        l = ((GF_ParentNode*)node)->children;
                        while (l) {
                                hsens = compositor_mpeg4_get_sensor_handler_ex(l->node, GF_TRUE);
                                if (hsens) {
                                        group->flags |= GROUP_HAS_SENSORS;
                                }
                                else if (get_light_type(l->node)) {
                                        group->flags |= GROUP_HAS_LIGHTS;
                                }
                                l = l->next;
                        }

                        /*now here is the trick: ExternProtos may not be loaded at this point, in which case we can't
                        perform proper culling. Unloaded ExternProto signal themselves by invalidating their parent
                        graph to get a new traversal. We must therefore reset the CHILD_DIRTY flag before computing
                        bounds otherwise we'll never re-invalidate the subgraph anymore*/
                        gf_node_dirty_clear(node, GF_SG_CHILD_DIRTY);
                }
        }
        /*not parent (eg form, layout...) sub-tree not dirty and getting bounds, direct copy */
        else if (tr_state->traversing_mode==TRAVERSE_GET_BOUNDS) {
                tr_state->bbox = group->bbox;
                if (!tr_state->bbox.is_set) tr_state->bbox.radius=-FIX_ONE;
                gf_node_dirty_clear(node, 0);
                return;
        }

        gf_node_dirty_clear(node, GF_SG_NODE_DIRTY);

        mode_back=tr_state->cull_flag;
        /*if culling not disabled*/
        if (!(group->flags & GROUP_SKIP_CULLING)
                /*for geometry AND lights*/
                && (tr_state->traversing_mode==TRAVERSE_SORT)
                /*do cull*/
                && !visual_3d_node_cull(tr_state, &group->bbox, 0)) {

                tr_state->cull_flag = mode_back;
                return;
        }


        /*picking: collect sensors*/
        sensor_backup = NULL;
        if ((tr_state->traversing_mode==TRAVERSE_PICK) && (group->flags & GROUP_HAS_SENSORS) ) {
                /*reset sensor stack if any sensors at this level*/
                sensor_backup = tr_state->vrml_sensors;
                tr_state->vrml_sensors = gf_list_new();

                l = ((GF_ParentNode*)node)->children;
                while (l) {
                        hsens = compositor_mpeg4_get_sensor_handler_ex(l->node, GF_TRUE);
                        if (hsens && hsens->IsEnabled(l->node))
                                gf_list_add(tr_state->vrml_sensors, hsens);

                        l = l->next;
                }
        }

        /*turn on local lights and global ones*/
        do_lights = 0;
        if (group->flags & GROUP_HAS_LIGHTS) {
                /*turn on global lights*/
                if (tr_state->traversing_mode==TRAVERSE_LIGHTING) {
                        l = ((GF_ParentNode*)node)->children;
                        while (l) {
                                if (get_light_type(l->node)==2) gf_node_traverse(l->node, tr_state);
                                l = l->next;
                        }
                }
                /*turn on local lights*/
                else if (tr_state->traversing_mode==TRAVERSE_SORT) {
                        do_lights = 1;
                        tr_state->traversing_mode = TRAVERSE_DRAW_3D;
                        tr_state->local_light_on = 1;

                        l = ((GF_ParentNode*)node)->children;
                        while (l) {
                                if (get_light_type(l->node)==1) {
                                        /*store lights for alpha draw*/
                                        dl = (DirectionalLightContext*)gf_malloc(sizeof(DirectionalLightContext));
                                        dl->dlight = l->node;
                                        memcpy(&dl->light_matrix, &tr_state->model_matrix, sizeof(GF_Matrix));
                                        gf_list_add(tr_state->local_lights, dl);
                                        /*and turn them on for non-alpha draw*/
                                        gf_node_traverse(dl->dlight, tr_state);
                                }
                                l = l->next;
                        }
                        tr_state->traversing_mode = TRAVERSE_SORT;
                }
        }


        if (tr_state->traversing_mode==TRAVERSE_GET_BOUNDS) {
                l = ((GF_ParentNode*)node)->children;
                split_text_backup = tr_state->text_split_mode;
                if (tr_state->text_split_mode && (gf_node_list_get_count(l)>1) ) tr_state->text_split_mode = 0;
                group->bbox.is_set = tr_state->bbox.is_set = 0;
                tr_state->bounds.width = 0;
                group->flags &= ~GROUP_SKIP_CULLING;

                while (l) {
                        gf_node_traverse(l->node, tr_state);
                        if (tr_state->disable_cull) {
                                group->flags |= GROUP_SKIP_CULLING;
                                tr_state->disable_cull = 0;
                        }
                        /*handle 2D nodes in 3D groups*/
                        if (tr_state->bounds.width) {
                                gf_bbox_from_rect(&tr_state->bbox, &tr_state->bounds);
                                tr_state->bounds.width = 0;
                        }

                        if (tr_state->bbox.is_set) {
                                gf_bbox_union(&group->bbox, &tr_state->bbox);
                        }
                        tr_state->bbox.is_set = 0;
                        l = l->next;
                }
                tr_state->bbox = group->bbox;
                if (group->flags & GROUP_SKIP_CULLING)
                        tr_state->disable_cull = 1;
                tr_state->text_split_mode = split_text_backup;
        } else {
                l = ((GF_ParentNode*)node)->children;
                while (l) {
                        gf_node_traverse(l->node, tr_state);
                        l = l->next;
                }

                if (tr_state->traversing_mode==TRAVERSE_SORT)
                        drawable3d_check_focus_highlight(node, tr_state, NULL);
        }
        tr_state->cull_flag = mode_back;

        if (sensor_backup) {
                /*destroy current traversing state sensors and restore previous*/
                gf_list_del(tr_state->vrml_sensors);
                tr_state->vrml_sensors = sensor_backup;
        }

        /*remove dlights*/
        if (do_lights) {
                u32 lcount;
                tr_state->traversing_mode = TRAVERSE_DRAW_3D;
                tr_state->local_light_on = 0;
                while ( (lcount = gf_list_count(tr_state->local_lights)) ) {
                        dl = (DirectionalLightContext*)gf_list_get(tr_state->local_lights, lcount-1);
                        gf_list_rem(tr_state->local_lights, lcount-1);
                        gf_node_traverse(dl->dlight, tr_state);
                        gf_free(dl);
                }
                /*and back to sort mode*/
                tr_state->traversing_mode = TRAVERSE_SORT;
        }
}


#endif


/*
 *      2D ParentNode tools - used by all nodes performing children layout
 */


void parent_node_setup(ParentNode2D *group)
{
        group->groups = gf_list_new();
}

void parent_node_predestroy(ParentNode2D *group)
{
        /*just in case*/
        parent_node_reset(group);
        gf_list_del(group->groups);
}

void parent_node_reset(ParentNode2D *group)
{
        while (gf_list_count(group->groups)) {
                ChildGroup *cg = (ChildGroup *)gf_list_get(group->groups, 0);
                gf_list_rem(group->groups, 0);
                gf_free(cg);
        }
}

void parent_node_start_group(ParentNode2D *group, GF_Node *n, Bool discardable)
{
        ChildGroup *cg;
        if (!n) {
                cg = gf_list_last(group->groups);
                if (!cg) return;
                n = cg->child;
        }
        GF_SAFEALLOC(cg, ChildGroup);
        if (!cg) {
                GF_LOG(GF_LOG_ERROR, GF_LOG_COMPOSE, ("[Compositor] Failed to allocate child group\n"));
                return;
        }
        cg->child = n;
        cg->text_type = discardable;
        gf_list_add(group->groups, cg);
}

void parent_node_end_group(ParentNode2D *group, GF_Rect *bounds)
{
        ChildGroup *cg = (ChildGroup *)gf_list_last(group->groups);
        if (!cg) return;
        /*don't override splitted text info*/
        if (cg->ascent || cg->descent) return;
        cg->original = *bounds;
        cg->final = cg->original;
}

void parent_node_end_text_group(ParentNode2D *group, GF_Rect *bounds, Fixed ascent, Fixed descent, u32 text_split_idx)
{
        ChildGroup *cg = (ChildGroup *)gf_list_last(group->groups);
        if (!cg) return;
        cg->text_split_idx = text_split_idx;
        cg->ascent = ascent;
        cg->descent = descent;
        cg->final = cg->original = *bounds;
}



void parent_node_traverse(GF_Node *node, ParentNode2D *group, GF_TraverseState *tr_state)
{
        Bool split_text_backup;
        GF_List *sensor_backup;
        GF_ChildNodeItem *l;

        if (gf_node_dirty_get(node) & GF_SG_CHILD_DIRTY) {
                Bool check_anchor=0;
                /*parent groups must recompute their bounds themselves since they modify children layout*/
                u32 ntag = gf_node_get_tag(node);
                group->flags &= ~GROUP_HAS_SENSORS;
                /*special case for anchor which is a parent node acting as a sensor*/
                if (ntag==TAG_MPEG4_Anchor) check_anchor=1;
#ifndef GPAC_DISABLE_X3D
                else if (ntag==TAG_X3D_Anchor) check_anchor=1;
#endif
                if (check_anchor) {
                        group->flags |= GROUP_HAS_SENSORS | GROUP_IS_ANCHOR;
                } else {
                        l = ((GF_ParentNode *)node)->children;
                        while (l) {
                                GF_SensorHandler *hsens = compositor_mpeg4_get_sensor_handler_ex(l->node, GF_TRUE);
                                if (hsens) {
                                        group->flags |= GROUP_HAS_SENSORS;
                                        break;
                                }
                                l = l->next;
                        }
                }

                /*now here is the trick: ExternProtos may not be loaded at this point, in which case we can't
                perform proper culling. Unloaded ExternProto signal themselves by invalidating their parent
                graph to get a new traversal. We must therefore reset the CHILD_DIRTY flag before computing
                bounds otherwise we'll never re-invalidate the subgraph anymore*/
                gf_node_dirty_clear(node, GF_SG_CHILD_DIRTY);
        }
        gf_node_dirty_clear(node, GF_SG_NODE_DIRTY);

        /*no culling in 2D*/

        /*picking: collect sensors*/
        sensor_backup = NULL;
        if ((tr_state->traversing_mode==TRAVERSE_PICK) && (group->flags & GROUP_HAS_SENSORS) ) {
                GF_SensorHandler *hsens;
                /*reset sensor stack if any sensors at this level*/
                sensor_backup = tr_state->vrml_sensors;
                tr_state->vrml_sensors = gf_list_new();


                /*add sensor(s) to traversing state*/
                l = ((GF_ParentNode *)node)->children;
                while (l) {
                        hsens = compositor_mpeg4_get_sensor_handler_ex(l->node, GF_TRUE);
                        if (hsens) gf_list_add(tr_state->vrml_sensors, hsens);
                        l = l->next;
                }
        }


        split_text_backup = tr_state->text_split_mode;

        group->flags &= ~GROUP_SKIP_CULLING;
        tr_state->bounds.width = tr_state->bounds.height = 0;
#ifndef GPAC_DISABLE_3D
        tr_state->bbox.is_set = 0;
#endif

        l = ((GF_ParentNode *)node)->children;
        while (l) {
                parent_node_start_group(group, l->node, 0);

                tr_state->bounds.width = tr_state->bounds.height = 0;

                gf_node_traverse(l->node, tr_state);

                /*handle 3D nodes in 2D groups*/
#ifndef GPAC_DISABLE_3D
                if (tr_state->bbox.is_set) {
                        gf_rect_from_bbox(&tr_state->bounds, &tr_state->bbox);
                        tr_state->bbox.is_set = 0;
                }
#endif
                parent_node_end_group(group, &tr_state->bounds);
                l = l->next;
        }
        tr_state->text_split_mode = split_text_backup;

        if (sensor_backup) {
                /*destroy current traversing state sensors and restore previous*/
                gf_list_del(tr_state->vrml_sensors);
                tr_state->vrml_sensors = sensor_backup;
        }
}

/*final drawing of each group*/
void parent_node_child_traverse(ChildGroup *cg, GF_TraverseState *tr_state)
{
        Fixed dx, dy;

        dx = cg->final.x - cg->original.x + cg->scroll_x;
        dy = cg->final.y - cg->original.y + cg->scroll_y;
        tr_state->text_split_idx = cg->text_split_idx;

#ifndef GPAC_DISABLE_3D
        if (tr_state->visual->type_3d) {
                GF_Matrix mx_bckup;

                gf_mx_copy(mx_bckup, tr_state->model_matrix);
                gf_mx_add_translation(&tr_state->model_matrix, dx, dy, 0);
                gf_node_traverse(cg->child, tr_state);
                gf_mx_copy(tr_state->model_matrix, mx_bckup);
        } else
#endif
        {
                GF_Matrix2D mx2d;
                gf_mx2d_copy(mx2d, tr_state->transform);
                gf_mx2d_init(tr_state->transform);
                gf_mx2d_add_translation(&tr_state->transform, dx, dy);
                gf_mx2d_add_matrix(&tr_state->transform, &mx2d);
                gf_node_traverse(cg->child, tr_state);
                gf_mx2d_copy(tr_state->transform, mx2d);
        }
        tr_state->text_split_idx = 0;
}

void parent_node_child_traverse_matrix(ChildGroup *cg, GF_TraverseState *tr_state, GF_Matrix2D *mat2D)
{
        if (!mat2D) return;

        tr_state->text_split_idx = cg->text_split_idx;
#ifndef GPAC_DISABLE_3D
        if (tr_state->visual->type_3d) {
                GF_Matrix mx, mx_bckup;
                gf_mx_from_mx2d(&mx, mat2D);
                gf_mx_copy(mx_bckup, tr_state->model_matrix);
                gf_mx_add_matrix(&tr_state->model_matrix, &mx);
                gf_node_traverse(cg->child, tr_state);
                gf_mx_copy(tr_state->model_matrix, mx_bckup);
        } else
#endif
        {
                GF_Matrix2D mx2d;

                gf_mx2d_copy(mx2d, tr_state->transform);
                gf_mx2d_pre_multiply(&tr_state->transform, mat2D);
                gf_node_traverse(cg->child, tr_state);
                gf_mx2d_copy(tr_state->transform, mx2d);
        }
        tr_state->text_split_idx = 0;
}

#endif /*GPAC_DISABLE_VRML*/

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