| /* | |
| * GPAC - Multimedia Framework C SDK | |
| * | |
| * Authors: Jean Le Feuvre | |
| * Copyright (c) Telecom ParisTech 2000-2023 | |
| * All rights reserved | |
| * | |
| * This file is part of GPAC / Scene Compositor sub-project | |
| * | |
| * GPAC is free software; you can redistribute it and/or modify | |
| * it under the terms of the GNU Lesser General Public License as published by | |
| * the Free Software Foundation; either version 2, or (at your option) | |
| * any later version. | |
| * | |
| * GPAC is distributed in the hope that it will be useful, | |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| * GNU Lesser General Public License for more details. | |
| * | |
| * You should have received a copy of the GNU Lesser General Public | |
| * License along with this library; see the file COPYING. If not, write to | |
| * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. | |
| * | |
| */ | |
| #include "nodes_stacks.h" | |
| #include "mpeg4_grouping.h" | |
| #include "visual_manager.h" | |
| #if !defined(GPAC_DISABLE_VRML) && !defined(GPAC_DISABLE_COMPOSITOR) | |
| /*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; | |
| gf_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; | |
| u32 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_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) ) { | |
| /*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) { | |
| GF_SensorHandler *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 //!defined(GPAC_DISABLE_VRML) && !defined(GPAC_DISABLE_COMPOSITOR) |